[mkgmap] 02/06: Imported Upstream version 0.0.0+svn3620

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sat Jun 27 12:14:05 UTC 2015


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

sebastic pushed a commit to branch master
in repository mkgmap.

commit 58c6665b5df7b32a0946265ccf05d2172e835fa6
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat Jun 27 13:33:43 2015 +0200

    Imported Upstream version 0.0.0+svn3620
---
 doc/addresses/address.txt                          |    6 +
 doc/options.txt                                    |   41 +-
 doc/styles/internal-tags.txt                       |    4 +-
 resources/help/en/options                          |   31 +-
 resources/mkgmap-version.properties                |    4 +-
 resources/styles/default/inc/address               |    4 +-
 resources/styles/default/inc/water_lines           |    1 +
 resources/styles/default/inc/water_polygons        |    3 +-
 resources/styles/default/lines                     |   13 +-
 resources/styles/default/points                    |    5 +-
 resources/styles/default/polygons                  |    5 +
 src/uk/me/parabola/imgfmt/app/Coord.java           |   33 +-
 src/uk/me/parabola/imgfmt/app/CoordNode.java       |    2 +
 src/uk/me/parabola/imgfmt/app/net/NETFile.java     |    4 +-
 .../me/parabola/imgfmt/app/net/NETFileReader.java  |   90 +-
 .../me/parabola/imgfmt/app/net/NumberPreparer.java |  178 +-
 src/uk/me/parabola/imgfmt/app/net/Numbers.java     |  393 +++-
 src/uk/me/parabola/imgfmt/app/net/RoadDef.java     |  216 ++-
 src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java |   39 +-
 .../parabola/imgfmt/app/trergn/LinePreparer.java   |   16 +-
 src/uk/me/parabola/imgfmt/app/trergn/Polyline.java |   16 +-
 src/uk/me/parabola/mkgmap/CommandArgsReader.java   |    6 +-
 src/uk/me/parabola/mkgmap/build/Locator.java       |    4 +-
 src/uk/me/parabola/mkgmap/build/LocatorUtil.java   |    6 +
 src/uk/me/parabola/mkgmap/build/MapBuilder.java    |  119 +-
 .../me/parabola/mkgmap/combiners/MdrBuilder.java   |   18 +-
 .../mkgmap/filters/LinePreparerFilter.java         |    2 +-
 .../mkgmap/filters/LineSizeSplitterFilter.java     |    2 +-
 src/uk/me/parabola/mkgmap/general/CityInfo.java    |  115 ++
 src/uk/me/parabola/mkgmap/general/MapDetails.java  |   37 +-
 src/uk/me/parabola/mkgmap/general/MapElement.java  |   13 +-
 src/uk/me/parabola/mkgmap/general/MapPoint.java    |   12 +-
 src/uk/me/parabola/mkgmap/general/MapRoad.java     |   87 +-
 src/uk/me/parabola/mkgmap/general/ZipCodeInfo.java |   77 +
 src/uk/me/parabola/mkgmap/main/MapMaker.java       |  174 --
 src/uk/me/parabola/mkgmap/main/StyleTester.java    |    7 +
 src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java |    4 +
 .../parabola/mkgmap/osmstyle/ExpressionRule.java   |    5 +
 src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java |    1 +
 src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java    |   17 +-
 .../parabola/mkgmap/osmstyle/StyledConverter.java  |  146 +-
 .../mkgmap/osmstyle/housenumber/ExtNumbers.java    | 1960 +++++++++++++++++++
 .../osmstyle/housenumber/HousenumberElem.java      |  110 ++
 .../osmstyle/housenumber/HousenumberGenerator.java | 2031 +++++++++++++++++---
 .../osmstyle/housenumber/HousenumberGroup.java     |  357 ++++
 .../osmstyle/housenumber/HousenumberIvl.java       |  502 +++++
 .../osmstyle/housenumber/HousenumberMatch.java     |  333 ++--
 .../osmstyle/housenumber/HousenumberRoad.java      |  803 ++++++++
 src/uk/me/parabola/mkgmap/reader/osm/Element.java  |   52 +-
 .../parabola/mkgmap/reader/osm/HighwayHooks.java   |    7 -
 .../mkgmap/reader/osm/HousenumberHooks.java        |  117 ++
 .../mkgmap/reader/osm/LinkDestinationHook.java     |   90 +-
 .../mkgmap/reader/osm/OsmMapDataSource.java        |   85 +-
 .../mkgmap/reader/osm/POIGeneratorHook.java        |   50 +-
 src/uk/me/parabola/mkgmap/reader/osm/Rule.java     |    2 +
 src/uk/me/parabola/mkgmap/reader/osm/Way.java      |    5 +-
 .../mkgmap/reader/osm/bin/OsmBinHandler.java       |   56 +-
 .../mkgmap/reader/osm/o5m/O5mBinHandler.java       |   14 +-
 .../mkgmap/reader/osm/xml/Osm5XmlHandler.java      |   42 +-
 .../parabola/mkgmap/reader/polish/RoadHelper.java  |   81 +-
 .../parabola/mkgmap/reader/test/AllElements.java   |   82 +-
 .../mkgmap/reader/test/ElementTestDataSource.java  |    2 +-
 .../MapPointKdTree.java => util/KdTree.java}       |   69 +-
 src/uk/me/parabola/util/Locatable.java             |   28 +
 src/uk/me/parabola/util/MultiHashSet.java          |   63 +
 src/uk/me/parabola/util/ShapeSplitter.java         |   12 +
 test/func/lib/NumberReader.java                    |   44 +-
 test/main/NumberRangeTest.java                     |    7 +-
 .../imgfmt/app/net/NumberPreparerTest.java         |    6 +-
 .../uk/me/parabola/imgfmt/app/net/NumbersTest.java |  139 ++
 .../KdTreeTest.java}                               |   13 +-
 71 files changed, 7844 insertions(+), 1274 deletions(-)

diff --git a/doc/addresses/address.txt b/doc/addresses/address.txt
index 10d28ad..5daa311 100644
--- a/doc/addresses/address.txt
+++ b/doc/addresses/address.txt
@@ -59,6 +59,12 @@ The style file can be used to assign the special mkgmap tags listed above.
 NOTE: All tags can be assigned without any restrictions but mkgmap performs some special processing for the +mkgmap:country+ tag. See chapter +Country names+ below.
 
 A common set of address rules is located in the +inc/address+ file in the default style of mkgmap.
+The tag mkgmap:execute_finalize_rules=true should be set for all objects that 
+should be considered for address search but may not appear in the map:
+addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
+
+You can also use the tag mkgmap:numbers=false to tell mkgmap
+that a node or way should be excluded when calculating address information.
 
 A common rule set to assign the +mkgmap:city+ tag looks like:
 ----
diff --git a/doc/options.txt b/doc/options.txt
index 667c328..1ba8eab 100644
--- a/doc/options.txt
+++ b/doc/options.txt
@@ -171,20 +171,26 @@ border have less quality.
 ;--housenumbers
 : Enables house number search for OSM input files. 
 
-: All nodes and polygons having addr:housenumber and addr:street set are matched 
+: All nodes and polygons having addr:housenumber set are matched 
 to streets. A match between a house number element and a street is created if
 the street is located within a radius of 150m and the addr:street tag value of 
-the house number element equals the mgkmap:street tag value of the street. 
+the house number element equals the mgkmap:street tag value of the street
+or the addr:street tag is missing and the number is plausible for the street.
 The mkgmap:street tag must be added to the street in the style file.
-
-: Example: 
+For optimal results, the tags mkgmap:city and mkgmap:postal_code should be
+set for the housenumber element. If a street connects two or more cities
+this allows to find all addresses along the road, even they have the same
+number.
+: Example for given street name: 
 :: Node -  addr:street=Main Street addr:housenumber=2
 :: Way 1 - name=Main Street
 :: Way 2 - name=Main Street, mkgmap:street=Main Street
-:: Way 3 - mkgmap:street=Mainstreet
+:: Way 3 - mkgmap:street=Mainstreet 
 :: Way 4 - name=Main Street [A504]
-: The node matches to Way 2. It has mkgmap:street set with a value equal to
+: The node 1 matches to Way 2. It has mkgmap:street set with a value equal to
 the addr:street tag value of the house  number node.
+: If the street is not given with addr:housenumber, mkgmap uses heuristics
+to find the best match. 
 
 === Overview map options ===
 ;--overview-mapname=name
@@ -455,7 +461,8 @@ those roads to report:
 
 
 ;--road-name-pois[=GarminCode]
-: 	Generate a POI for each named road. By default, the POIs'
+:   Now ignored, former meaning:   	
+		Generate a POI for each named road. By default, the POIs'
 Garmin type code is 0x640a. If desired, a different type code
 can be specified with this option.  This s a workaround for not
 being able to search for roads.
@@ -614,19 +621,20 @@ to the next junction or a length of ~25 m. The tags
 are ignored for pedestrian-only ways.
 
 ;--process-destination
-: 	Splits all motorway_link and trunk_link ways tagged with 
-destination	into two or three parts where the second part
-is additionally tagged with mkgmap:dest_hint=true. This 
-allows to use any routable Garmin type (except 0x08 and 0x09)
+: 	Splits all motorway_link, trunk_link, and primary_link 
+ways tagged with destination	into two or three parts where 
+the second part is additionally tagged with mkgmap:dest_hint=true. 
+This allows using any routable Garmin type (except 0x08 and 0x09)
 for that part so that the Garmin device tells the name of
 this part as hint which destination to follow.
+See also --process-exits.
 
 ;--process-exits
 :     Usual Garmin devices do not tell the name of the exit on motorways 
-while routing with mkgmap created maps. This option splits the each
-motorway_link and trunk_link into three parts. All parts are tagged 
-with the original tags of the link. Additionally the middle part is 
-tagged with the following tags:
+while routing with mkgmap created maps. This option splits each
+motorway_link, trunk_link and primary_link way into three parts. 
+All parts are tagged with the original tags of the link. 
+Additionally the middle part is tagged with the following tags:
   mkgmap:exit_hint=true
   mkgmap:exit_hint_ref=<ref tag value of the exit>
   mkgmap:exit_hint_name=<name tag value of the exit>
@@ -635,7 +643,8 @@ tagged with the following tags:
 : Adding a rule checking the mkgmap:exit_hint=true makes it possible
 to use any routable Garmin type (except 0x08 and 0x09) for the middle 
 part so that the Garmin device tells the name of this middle part as 
-hint where to leave the motorway/trunk. 
+hint where to leave the motorway/trunk. The first part must have type
+0x08 or 0x09 so that Garmin uses the hint. 
 
 ;--delete-tags-file=FILENAME
 : 	Names a file that should contain one or more lines of the form
diff --git a/doc/styles/internal-tags.txt b/doc/styles/internal-tags.txt
index be2bd71..2420a07 100644
--- a/doc/styles/internal-tags.txt
+++ b/doc/styles/internal-tags.txt
@@ -130,7 +130,9 @@ is used to assign the country location.
 |=========================================================
 | Tag | Description     
 | +mkgmap:skipSizeFilter+  | If set to +true+ the line or polygon will pass the size filter, no matter what size it has    
-| +mkgmap:highest-resolution-only+  | If set to +true+ the object will only be added for the highest resolution configured in the element type definition.    
+| +mkgmap:highest-resolution-only+  | If set to +true+ the object will only be added for the highest resolution configured in the element type definition.
+| +mkgmap:execute_finalize_rules+  | If set to +true+ mkgmap will execute the finalize rules even if no object is created fot the element.
+| +mkgmap:numbers+  | If set to +false+ for a node or way mkgmap will ignore the object in the calculations for the --housenumber option   
 |=========================================================
 
 
diff --git a/resources/help/en/options b/resources/help/en/options
index 61b19d0..43d90ec 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -177,12 +177,16 @@ Address search options:
 
 --housenumbers
   Enables house number search for OSM input files. 
-  All nodes and polygons having addr:housenumber and addr:street set are matched 
+  All nodes and polygons having addr:housenumber set are matched 
   to streets. A match between a house number element and a street is created if
   the street is located within a radius of 150m and the addr:street tag value of 
   the house number element equals the mgkmap:street tag value of the street. 
   The mkgmap:street tag must be added to the street in the style file.
-  Example: 
+  For optimal results, the tags mkgmap:city and mkgmap:postal_code should be
+  set for the housenumber element. If a street connects two or more cities
+  this allows to find all addresses along the road, even they have the same
+  number.
+  Example for given street name: 
      Node -  addr:street=Main Street addr:housenumber=2
      Way 1 - name=Main Street
      Way 2 - name=Main Street, mkgmap:street=Main Street
@@ -190,6 +194,8 @@ Address search options:
      Way 4 - name=Main Street [A504]
     The node matches to Way 2. It has mkgmap:street set with a value equal to
     the addr:street tag value of the house number node.
+  If the street is not given with addr:housenumber, mkgmap uses heuristics
+	to find the best match.
   
 Overview map options:
 --overview-mapname=name
@@ -456,6 +462,7 @@ Miscellaneous options:
 	that go nowhere.
 
 --road-name-pois[=GarminCode]
+	Now ignored, former usage:
 	Generate a POI for each named road. By default, the POIs'
 	Garmin type code is 0x640a. If desired, a different type code
 	can be specified with this option.  This is a workaround for not
@@ -611,19 +618,20 @@ Miscellaneous options:
     are ignored for pedestrian-only ways.      
 
 --process-destination
-	Splits all motorway_link and trunk_link ways tagged with 
-	destination	into two or three parts where the second part
-	is additionally tagged with mkgmap:dest_hint=true. This 
-	allows to use any routable Garmin type (except 0x08 and 0x09)
+	Splits all motorway_link, trunk_link, and primary_link 
+	ways tagged with destination into two or three parts where 
+	the second part is additionally tagged with mkgmap:dest_hint=true. 
+	This allows to use any routable Garmin type (except 0x08 and 0x09)
 	for that part so that the Garmin device tells the name of
 	this part as hint which destination to follow.
+	See also --process-exits.
 	
 --process-exits
     Usual Garmin devices do not tell the name of the exit on motorways 
-    while routing with mkgmap created maps. This option splits the each
-    motorway_link and trunk_link into three parts. All parts are tagged 
-    with the original tags of the link. Additionally the middle part is 
-    tagged with the following tags:
+    while routing with mkgmap created maps. This option splits each
+    motorway_link, trunk_link and primary_link way into three parts. 
+		All parts are tagged with the original tags of the link. 
+		Additionally the middle part is tagged with the following tags:
       mkgmap:exit_hint=true
       mkgmap:exit_hint_ref=<ref tag value of the exit>
       mkgmap:exit_hint_name=<name tag value of the exit>
@@ -631,7 +639,8 @@ Miscellaneous options:
     Adding a rule checking the mkgmap:exit_hint=true makes it possible
     to use any routable Garmin type (except 0x08 and 0x09) for the middle 
     part so that the Garmin device tells the name of this middle part as 
-    hint where to leave the motorway/trunk. 
+    hint where to leave the motorway/trunk.
+		The first part must have type 0x08 or 0x09 so that Garmin uses the hint. 
 	
 --delete-tags-file=FILENAME
 	Names a file that should contain one or more lines of the form
diff --git a/resources/mkgmap-version.properties b/resources/mkgmap-version.properties
index de8be19..63d1375 100644
--- a/resources/mkgmap-version.properties
+++ b/resources/mkgmap-version.properties
@@ -1,2 +1,2 @@
-svn.version: 3598
-build.timestamp: 2015-05-18T07:21:27+0100
+svn.version: 3620
+build.timestamp: 2015-06-10T11:09:22+0100
diff --git a/resources/styles/default/inc/address b/resources/styles/default/inc/address
index 4a85973..d75e32f 100644
--- a/resources/styles/default/inc/address
+++ b/resources/styles/default/inc/address
@@ -36,15 +36,13 @@ mkgmap:country=POL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='$
 mkgmap:country=POL & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4|subst:województwo =>}' }
 
 # other european countries
-mkgmap:country=BEL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
+mkgmap:country=BEL & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
 mkgmap:country=CZE & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=CZE & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7}' }
 mkgmap:country=DNK & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=DNK & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7}' }
 mkgmap:country=FIN & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
 mkgmap:country=FIN & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
-mkgmap:country=FRA & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
-mkgmap:country=FRA & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=ISL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=ITA & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=LUX & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
diff --git a/resources/styles/default/inc/water_lines b/resources/styles/default/inc/water_lines
index e829a8a..898e28e 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 {set mkgmap:numbers = false }
 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]
 
diff --git a/resources/styles/default/inc/water_polygons b/resources/styles/default/inc/water_polygons
index 5fb376a..d59f0fd 100644
--- a/resources/styles/default/inc/water_polygons
+++ b/resources/styles/default/inc/water_polygons
@@ -1,4 +1,5 @@
-landuse=basin|landuse=reservoir [0x3f resolution 20]
+landuse=basin [0x3f resolution 20]
+(landuse=reservoir | (natural=water & water=reservoir)) [0x3f resolution 20]
 
 natural=bay [0x3d resolution 18]
 natural=glacier [0x4d resolution 18]
diff --git a/resources/styles/default/lines b/resources/styles/default/lines
index 391220a..70192ce 100644
--- a/resources/styles/default/lines
+++ b/resources/styles/default/lines
@@ -8,6 +8,7 @@
 # and http://wiki.openstreetmap.org/wiki/Mkgmap/help/Custom_styles
 # for more information.
 
+addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
 aeroway=runway [0x27 resolution 20]
 aeroway=taxiway [0x27 resolution 24]
 
@@ -27,12 +28,12 @@ highway=* & oneway=yes & (access=private|access=no)
 {add mkgmap:dead-end-check=false}
 # Validation-like checks (uncomment to enable)
 #highway=motorway_link & oneway!=yes & oneway!=no { echo "motorway_link lacks oneway" }
-highway=motorway|highway=motorway_link { add oneway=yes }
+highway=motorway|highway=motorway_link { add oneway=yes; add mkgmap:numbers=false }
 
 # Set highway names to include the reference if there is one
 highway=motorway { name '${ref|highway-symbol:hbox} ${name}' | '${ref|highway-symbol:hbox}' | '${name}' }
 
-(highway=motorway_link | highway=trunk_link) & mkgmap:exit_hint=true & mkgmap:dest_hint=true
+(highway=motorway_link | highway=trunk_link | highway=primary_link) & mkgmap:exit_hint=true & mkgmap:dest_hint=true
   { name '${destination:ref|subst: =>} ${destination|subst:;=> |subst:/=> }' | 
          '${ref|subst: =>} ${destination|subst:;=> |subst:/=> }' | 
          '${destination|subst:;=> |subst:/=> }' |
@@ -43,13 +44,13 @@ highway=motorway { name '${ref|highway-symbol:hbox} ${name}' | '${ref|highway-sy
          'Exit ${mkgmap:exit_hint_ref}'
        }
 
-(highway=motorway_link | highway=trunk_link) & mkgmap:exit_hint!=* & mkgmap:dest_hint=true
+(highway=motorway_link | highway=trunk_link | highway=primary_link) & mkgmap:exit_hint!=* & mkgmap:dest_hint=true
   { name '${destination:ref|subst: =>} ${destination|subst:;=> |subst:/=> }' |
          '${ref|subst: =>} ${destination|subst:;=> |subst:/=> }' | 
          '${destination|subst:;=> |subst:/=> }'
        }
 
-(highway=motorway_link | highway=trunk_link) & mkgmap:exit_hint=true & mkgmap:dest_hint!=*
+(highway=motorway_link | highway=trunk_link | highway=primary_link) & mkgmap:exit_hint=true & mkgmap:dest_hint!=*
   { name 'Exit ${mkgmap:exit_hint_ref} ${mkgmap:exit_hint_name}' | 
          'Exit ${mkgmap:exit_hint_ref} ${mkgmap:exit_hint_exit_to}' | 
          'Exit ${mkgmap:exit_hint_exit_to}' |
@@ -125,6 +126,7 @@ highway=trunk_link [0x09 road_class=3 road_speed=2 resolution 20]
 highway=* & highway!=proposed & motorroad=yes [0x02 road_class=4 road_speed=4 resolution 18]
 highway=primary & ( network=e-road | int_ref=* ) [0x03 resolution 17-18 continue]
 highway=primary [0x03 road_class=3 road_speed=4 resolution 19]
+highway=primary_link & (mkgmap:exit_hint=true | mkgmap:dest_hint=true)[0x06 road_class=3 road_speed=1 resolution 21]
 highway=primary_link [0x08 road_class=3 road_speed=1 resolution 21]
 highway=secondary & ( network=e-road | int_ref=* ) [0x04 resolution 18-19 continue]
 highway=secondary [0x04 road_class=2 road_speed=3 resolution 20]
@@ -197,6 +199,9 @@ include 'inc/access';
 #limit artificial cycleways to to resolution 24
 mkgmap:synthesised=yes & mkgmap:bicycle=yes { set mkgmap:highest-resolution-only = true }
 
+# don't add house numbers to unnamed or artifical bicycle ways
+mkgmap:bicycle=yes & (mkgmap:foot=no & mkgmap:car=no & mkgmap:street!=* | mkgmap:synthesised=yes) {set mkgmap:numbers=false}
+
 name=* { name '${name}' }
 
 highway=* & ref=* { addlabel '${ref}' }
diff --git a/resources/styles/default/points b/resources/styles/default/points
index 2e030a4..bc4b7a3 100644
--- a/resources/styles/default/points
+++ b/resources/styles/default/points
@@ -8,6 +8,7 @@
 # and http://wiki.openstreetmap.org/wiki/Mkgmap/help/Custom_styles
 # for more information.
 
+addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
 barrier=* & bicycle=*    { set mkgmap:bicycle='${bicycle|subst:private=>no}' }
 barrier=* & foot=*       { set mkgmap:foot='${foot|subst:private=>no}' }                  
 barrier=* & hgv=*        { set mkgmap:truck='${hgv|subst:private=>no}' }                  
@@ -272,8 +273,8 @@ tourism=attraction & historic=* [0x2c02 resolution 24]
 tourism=attraction [0x2c04 resolution 24]
 tourism=artwork [0x2c04 resolution 24]
 tourism=aquarium [0x2c07 resolution 24]
-tourism=camp_site [0x2b03 resolution 24]
-tourism=caravan_site [0x2b03 resolution 24]
+tourism=camp_site [0x2b05 resolution 24]
+tourism=caravan_site [0x2b05 resolution 24]
 tourism=chalet [0x2b02 resolution 24]
 tourism=guest_house [0x2b02 resolution 24]
 tourism=hostel [0x2b02 resolution 24]
diff --git a/resources/styles/default/polygons b/resources/styles/default/polygons
index 825c09c..4591fda 100644
--- a/resources/styles/default/polygons
+++ b/resources/styles/default/polygons
@@ -8,6 +8,8 @@
 # and http://wiki.openstreetmap.org/wiki/Mkgmap/help/Custom_styles
 # for more information.
 
+addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
+
 leisure=* & sport=* & name=* { set name='${name} (${sport})' }
 leisure=* & sport=* & name!=* { add name='${sport}' }
 
@@ -80,4 +82,7 @@ include 'inc/landuse_polygons';
 <finalize>
 # The finalizer section is executed for each element when a rule with an element type matches
 
+# we need addrees info from buildings for the address search
+include 'inc/address';
+
 name=* { name '${name}' }
diff --git a/src/uk/me/parabola/imgfmt/app/Coord.java b/src/uk/me/parabola/imgfmt/app/Coord.java
index eb93e71..89dc0f7 100644
--- a/src/uk/me/parabola/imgfmt/app/Coord.java
+++ b/src/uk/me/parabola/imgfmt/app/Coord.java
@@ -48,6 +48,7 @@ public class Coord implements Comparable<Coord> {
 	private final static short PART_OF_BAD_ANGLE = 0x0080; // bit in flags is true if point should be treated as a node
 	private final static short PART_OF_SHAPE2 = 0x0100; // use only in ShapeMerger
 	private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer
+	private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
 	
 	public final static int HIGH_PREC_BITS = 30;
 	public final static int DELTA_SHIFT = 6;
@@ -177,7 +178,7 @@ public class Coord implements Comparable<Coord> {
 	}
 
 	public boolean preserved() {
-		return (flags & PRESERVED_MASK) != 0;
+		return (flags & PRESERVED_MASK) != 0 || (flags & HOUSENUMBER_NODE) != 0;
 	}
 
 	public void preserved(boolean preserved) {
@@ -331,6 +332,24 @@ public class Coord implements Comparable<Coord> {
 			this.flags &= ~END_OF_WAY; 
 	}
 
+	/**
+	 * @return if this is the beginning/end of a house number interval 
+	 */
+	public boolean isNumberNode(){
+		return (flags & HOUSENUMBER_NODE) != 0;
+	}
+	
+	/**
+	 * Set or unset flag for {@link WrongAngleFixer} 
+	 * @param b true or false
+	 */
+	public void setNumberNode(boolean b) {
+		if (b) 
+			this.flags |= HOUSENUMBER_NODE;
+		else 
+			this.flags &= ~HOUSENUMBER_NODE; 
+	}
+	
 	public int hashCode() {
 		// Use a factor for latitude to span over the whole integer range:
 		// max lat: 4194304
@@ -339,6 +358,9 @@ public class Coord implements Comparable<Coord> {
 		return 503 * latitude + longitude;
 	}
 
+	/**
+	 * Compares the coordinates that are displayed in the map
+	 */
 	public boolean equals(Object obj) {
 		if (obj == null || !(obj instanceof Coord))
 			return false;
@@ -346,6 +368,13 @@ public class Coord implements Comparable<Coord> {
 		return latitude == other.latitude && longitude == other.longitude;
 	}
 	
+	/**
+	 * Compares the coordinates using the delta values. 
+	 * XXX: Note that 
+	 * p1.highPrecEquals(p2) is not always equal to p1.equals(p2)
+	 * @param other
+	 * @return
+	 */
 	public boolean highPrecEquals(Coord other) {
 		if (other == null)
 			return false;
@@ -547,7 +576,7 @@ public class Coord implements Comparable<Coord> {
 	}
 
 	public String toDegreeString() {
-		return String.format(Locale.ENGLISH, "%.6f/%.6f",
+		return String.format(Locale.ENGLISH, "%.6f,%.6f",
 			getLatDegrees(),
 			getLonDegrees());
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/CoordNode.java b/src/uk/me/parabola/imgfmt/app/CoordNode.java
index d73dfb0..806c8aa 100644
--- a/src/uk/me/parabola/imgfmt/app/CoordNode.java
+++ b/src/uk/me/parabola/imgfmt/app/CoordNode.java
@@ -37,6 +37,7 @@ public class CoordNode extends Coord {
 		super(latitude, longitude);
 		this.id = id;
 		setOnBoundary(boundary);
+		setNumberNode(true);
 		preserved(true);
 	}
 
@@ -44,6 +45,7 @@ public class CoordNode extends Coord {
 		super(other);
 		this.id = id;
 		setOnBoundary(boundary);
+		setNumberNode(true);
 		preserved(true);
 		
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/net/NETFile.java b/src/uk/me/parabola/imgfmt/app/net/NETFile.java
index eed440b..d871d93 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NETFile.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NETFile.java
@@ -128,7 +128,7 @@ public class NETFile extends ImgFile {
 				SortKey<LabeledRoadDef> nameKey = sort.createSortKey(lrd, label, 0, cache);
 
 				// If there is a city add it to the sort.
-				City city = rd.getCity();
+				City city = (rd.getCities().isEmpty() ? null : rd.getCities().get(0)); // what if we more than one?
 				SortKey<LabeledRoadDef> cityKey;
 				if (city != null) {
 					int region = city.getRegionNumber();
@@ -160,7 +160,7 @@ public class NETFile extends ImgFile {
 
 			Label name = lrd.label;
 			RoadDef road = lrd.roadDef;
-			City city = road.getCity();
+			City city = (road.getCities().isEmpty() ? null : road.getCities().get(0)); // what if we more than one?
 
 			if (road.hasHouseNumbers() || !name.equals(lastName) || city != lastCity) {
 
diff --git a/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java b/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java
index 0d09b1e..e15d84d 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java
@@ -12,6 +12,8 @@
  */
 package uk.me.parabola.imgfmt.app.net;
 
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -111,9 +113,15 @@ public class NETFileReader extends ImgFile {
 				int zipFlag = (flags2 >> 10) & 0x3;
 				int cityFlag = (flags2 >> 12) & 0x3;
 				int numberFlag = (flags2 >> 14) & 0x3;
-
-				road.setZip(fetchZipCity(reader, zipFlag, zips, zipSize));
-				road.setCity(fetchZipCity(reader, cityFlag, cities, citySize));
+				IntArrayList indexes = new IntArrayList();
+				fetchZipCityIndexes(reader, zipFlag, zipSize, indexes);
+				for (int index : indexes){
+					road.addZipIfNotPresent(zips.get(index));
+				}
+				fetchZipCityIndexes(reader, cityFlag, citySize, indexes);
+				for (int index : indexes){
+					road.addCityIfNotPresent(cities.get(index));
+				}
 
 				fetchNumber(reader, numberFlag);
 			}
@@ -132,31 +140,83 @@ public class NETFileReader extends ImgFile {
 	}
 
 	/**
-	 * Fetch a zip or a city.
-	 * @param <T> Can be city or zip.
-	 * @return The found City or Zip.
+	 * Parse a list of zip/city indexes.
+	 * @param reader
+	 * @param flag
+	 * @param size
+	 * @param indexes
 	 */
-	private <T> T fetchZipCity(ImgFileReader reader, int flag, List<T> list, int size) {
-		T item = null;
+	private void fetchZipCityIndexes(ImgFileReader reader, int flag, int size, IntArrayList indexes) {
+		indexes.clear();
 		if (flag == 2) {
 			// fetch city/zip index
 			int ind = (size == 2)? reader.getChar(): (reader.get() & 0xff);
 			if (ind != 0)
-				item = list.get(ind-1);
+				indexes.add(ind-1);
 		} else if (flag == 3) {
 			// there is no item
 		} else if (flag == 0) {
-			// Skip over these
-			int n = reader.get();
-			reader.get(n);
+			int n = reader.get() & 0xff;
+			parseList(reader, n, size, indexes);
 		} else if (flag == 1) {
-			// Skip over these
 			int n = reader.getChar();
-			reader.get(n);
+			parseList(reader, n, size, indexes);
 		} else {
 			assert false : "flag is " + flag;
 		}
-		return item;
+	}
+	
+	private void parseList(ImgFileReader reader, int n, int size,
+			IntArrayList indexes) {
+		long endPos = reader.position() + n;
+		int node = 0; // not yet used
+		while (reader.position() < endPos) {
+			int initFlag = reader.get() & 0xff;
+			int skip = (initFlag & 0x1f);
+			initFlag >>= 5;
+			if (initFlag == 7) {
+				// Need to read another byte
+				initFlag = reader.get() & 0xff;
+				skip |= ((initFlag & 0x1f) << 5);
+				initFlag >>= 5;
+			}
+			node += skip + 1;
+			int right = 0, left = 0;
+			if (initFlag == 0) {
+				right = left = getCityOrZip(reader, size, endPos);
+			} else if ((initFlag & 0x4) != 0) {
+				if ((initFlag & 1) == 0)
+					right = 0;
+				if ((initFlag & 2) == 0)
+					left = 0;
+			} else {
+				if ((initFlag & 1) != 0)
+					left = getCityOrZip(reader, size, endPos);
+				if ((initFlag & 2) != 0)
+					right = getCityOrZip(reader, size, endPos);
+			}
+			if (left > 0)
+				indexes.add(left - 1);
+			if (right > 0 && left != right)
+				indexes.add(right - 1);
+		}
+	}
+
+	private int getCityOrZip(ImgFileReader reader, int size, long endPos) {
+		if (reader.position() > endPos - size) {
+			assert false : "ERRROR overflow";
+			return 0;
+		}
+		int cnum;
+		if (size == 1)
+			cnum = reader.get() & 0xff;
+		else if (size == 2)
+			cnum = reader.getChar();
+		else {
+			assert false : "unexpected size value" + size;
+			return 0;
+		}
+		return cnum;
 	}
 
 	/**
diff --git a/src/uk/me/parabola/imgfmt/app/net/NumberPreparer.java b/src/uk/me/parabola/imgfmt/app/net/NumberPreparer.java
index 3224841..aa80147 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NumberPreparer.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NumberPreparer.java
@@ -12,10 +12,17 @@
  */
 package uk.me.parabola.imgfmt.app.net;
 
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.BitWriter;
+import uk.me.parabola.imgfmt.app.lbl.City;
+import uk.me.parabola.imgfmt.app.lbl.Zip;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.CityInfo;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
 
 import static uk.me.parabola.imgfmt.app.net.NumberStyle.*;
 
@@ -29,7 +36,7 @@ import static uk.me.parabola.imgfmt.app.net.NumberStyle.*;
  * @author Steve Ratcliffe
  */
 public class NumberPreparer {
-
+	private static final Logger log = Logger.getLogger(NumberPreparer.class);
 	private final List<Numbers> numbers;
 	private boolean valid;
 
@@ -39,11 +46,31 @@ public class NumberPreparer {
 
 	private BitWriter bw;
 	private boolean swappedDefaultStyle;
+	CityZipWriter zipWriter;
+	CityZipWriter cityWriter;
 
 	public NumberPreparer(List<Numbers> numbers) {
 		this.numbers = numbers;
+		this.zipWriter = new CityZipWriter("zip", 0, 0);
+		this.cityWriter = new CityZipWriter("city", 0, 0);
 	}
 
+	
+	public NumberPreparer(List<Numbers> numbers, Zip zip, City city, int numCities, int numZips) {
+		this.numbers = numbers;
+		
+		zipWriter = new CityZipWriter("zip",(zip == null) ? 0: zip.getIndex(), numZips);
+		cityWriter = new CityZipWriter("city",(city == null) ? 0: city.getIndex(), numCities);
+	}
+
+	public boolean prepare(){
+		fetchBitStream();
+		if (!valid)
+			return false;
+		zipWriter.compile(numbers);
+		cityWriter.compile(numbers);
+		return true;
+	}
 	/**
 	 * Make the bit stream and return it. This is only done once, if you call this several times
 	 * the same bit writer is returned every time.
@@ -52,7 +79,6 @@ public class NumberPreparer {
 	public BitWriter fetchBitStream() {
 		if (bw != null)
 			return bw;
-
 		int initialValue = setup();
 
 		// Write the bitstream
@@ -75,13 +101,13 @@ public class NumberPreparer {
 			if (bw.getLength() > 1)
 				valid = true;
 		} catch (Abandon e) {
-			System.out.println(e.getMessage());
+			log.error(e.getMessage());
 			valid = false;
 		}
 
 		return bw;
 	}
-
+	
 	/**
 	 * Do some initial calculation and sanity checking of the numbers that we are to
 	 * write.
@@ -136,12 +162,11 @@ public class NumberPreparer {
 
 		int lastNode = -1;
 		for (Numbers n : numbers) {
-			if (!n.hasRnodNumber())
+			if (!n.hasIndex())
 				throw new Abandon("no r node set");
-
 			// See if we need to skip some nodes
-			if (n.getRnodNumber() != lastNode + 1)
-				state.writeSkip(bw, n.getRnodNumber() - lastNode - 2);
+			if (n.getIndex() != lastNode + 1)
+				state.writeSkip(bw, n.getIndex() - lastNode - 2);
 
 			// Normal case write out the next node.
 			state.setTarget(n);
@@ -152,7 +177,7 @@ public class NumberPreparer {
 			state.writeNumbers(bw);
 			state.restoreWriters();
 
-			lastNode = n.getRnodNumber();
+			lastNode = n.getIndex();
 		}
 	}
 
@@ -207,6 +232,10 @@ public class NumberPreparer {
 	 * @return True if the preparer believes that the output is valid.
 	 */
 	public boolean isValid() {
+		try {
+			fetchBitStream();
+		} catch (Exception e) {
+		}
 		return valid;
 	}
 
@@ -751,8 +780,10 @@ public class NumberPreparer {
 			}
 		}
 	}
+	
 }
 
+
 /**
  * A bit writer that can be configured with different bit width and sign properties.
  *
@@ -849,3 +880,132 @@ class Abandon extends RuntimeException {
 		super("HOUSE NUMBER RANGE: " + message);
 	}
 }
+	
+class CityZipWriter {
+	private ByteArrayOutputStream buf; 
+	private final String type;
+	private final int numItems;
+	private final int defaultIndex;
+	
+	
+	public CityZipWriter(String type, int defIndex, int numItems) {
+		this.type = type;
+		this.defaultIndex = defIndex;
+		this.numItems = numItems;
+		buf = new ByteArrayOutputStream();
+	}
+	
+	public ByteArrayOutputStream getBuffer(){
+		return buf;
+	}
+	public boolean compile(List<Numbers> numbers){
+		try {
+			int lastNodeIndex = -1;
+			// left and right entry in zip or city table
+			int []prevIndexes = new int[2]; 
+			prevIndexes[0] = prevIndexes[1] = -1;
+			int []indexes = new int[2]; 
+			for (Numbers num : numbers){
+				for (int i = 0; i < 2; i++){
+					indexes[i] = -1;
+					boolean left = (i == 0);
+					switch (type) {
+					case "zip":
+						ZipCodeInfo zipInfo = num.getZipCodeInfo(left);
+						if (zipInfo != null){
+							if (zipInfo.getImgZip() != null)
+								indexes[i] = zipInfo.getImgZip().getIndex();
+							else 
+								indexes[i] = 0; // or default?
+						}
+						break;
+					case "city": 
+						CityInfo cityInfo = num.getCityInfo(left);
+						if (cityInfo != null){
+							if (cityInfo.getImgCity() != null)
+								indexes[i] = cityInfo.getImgCity().getIndex();
+							else 
+								indexes[i] = 0; // or default?
+						}
+						break;
+					default:
+						break;
+					}
+				}
+				if (indexes[0] < 0 && indexes[1] < 0)
+					continue;
+				if (lastNodeIndex < 0){
+					if (num.getIndex() > 0 ){ 
+						int [] defindexes = {defaultIndex,defaultIndex};
+						write(0, defindexes, prevIndexes);
+					}
+				}
+				int skip = num.getIndex() - lastNodeIndex - 1;
+				assert defaultIndex > 0 : "bad default index";
+				lastNodeIndex = num.getIndex();
+				if (indexes[0] < 0)
+					indexes[0] = defaultIndex;
+				if (indexes[1] < 0)
+					indexes[1] = defaultIndex;
+				write(skip, indexes, prevIndexes);
+			}
+		} catch (Abandon e) {
+			return false;
+		}
+		return true;
+	}
+
+	private void write(int skip, int[] indexes, int[] prevIndexes) {
+		if (Arrays.equals(indexes, prevIndexes))
+			return;
+		// we can signal new values for left and / or right side 
+		int sidesFlag = 0;  
+		if (indexes[0] <= 0 && indexes[1] <= 0){
+			sidesFlag |= 4; // signal end of a zip code/city interval
+			if (indexes[0] == 0)
+				sidesFlag |= 1;
+			if (indexes[1] == 0)
+				sidesFlag |= 2;
+		} else {
+			if (indexes[1] != indexes[0]){
+				if (indexes[0] > 0 && indexes[0] != prevIndexes[0])
+					sidesFlag |= 1;
+				if (indexes[1] > 0 && indexes[1] != prevIndexes[1])
+					sidesFlag |= 2;
+			}
+		}
+
+		int initFlag = Math.max(skip-1,0);
+		if (initFlag > 31){
+			// we have to write two bytes
+			buf.write((byte) (initFlag & 0x1f | 0x7<<5));
+			initFlag >>= 5;
+		}
+		initFlag |= sidesFlag << 5;
+		if ((initFlag & 0xff) == 4){
+			long dd = 4;
+		}
+		buf.write((byte) (initFlag & 0xff));
+		if ((sidesFlag & 4) == 0) {
+			if (indexes[0] > 0 && (sidesFlag == 0 || (sidesFlag & 1) == 1))
+				writeIndex(indexes[0]);
+			if (indexes[1] > 0 && (sidesFlag & 2) != 0)
+				writeIndex(indexes[1]);
+		} else {
+			long dd = 4;
+		}
+		System.arraycopy(indexes, 0, prevIndexes, 0, indexes.length);
+	}
+	
+	void writeIndex(int val){
+		if (val <= 0)
+			return;
+		if (numItems > 255){
+			buf.write((byte) val & 0xff);
+			buf.write((byte) (val >> 8));
+		}
+		else 
+			buf.write((byte) val);
+	}
+
+}
diff --git a/src/uk/me/parabola/imgfmt/app/net/Numbers.java b/src/uk/me/parabola/imgfmt/app/net/Numbers.java
index 43bd9c0..2ecf3e6 100644
--- a/src/uk/me/parabola/imgfmt/app/net/Numbers.java
+++ b/src/uk/me/parabola/imgfmt/app/net/Numbers.java
@@ -13,6 +13,8 @@
 package uk.me.parabola.imgfmt.app.net;
 
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.CityInfo;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
 
 /**
  * Describes the house numbering from a node in the road.
@@ -20,131 +22,268 @@ import uk.me.parabola.log.Logger;
  */
 public class Numbers {
 	private static final Logger log = Logger.getLogger(Numbers.class);
+	public static final boolean LEFT = true;
+	public static final boolean RIGHT = false;
+
+	private static final int MAX_DELTA = 131071; // see NumberPreparer
 
 	// The node in the road where these numbers apply.  In the polish notation it is the
-	// node in the road, whereas in the NET file it is the number of the routing node.
+	// node in the road, whereas in the NET file it is the index of the number node.
 	private int nodeNumber; // node in road index
-	private Integer rnodNumber; // routing node index
+	private Integer indexNumber; // the position in the list of Numbers (starting with 0)
+	// the data on side of the road
+	private RoadSide leftSide,rightSide;
+
+	private class RoadSide {
+		NumDesc numbers;
+		// to be added
+		CityInfo cityInfo;
+		ZipCodeInfo zipCode;
+		boolean isEmpty(){
+			return cityInfo == null && zipCode == null && numbers == null;
+		}
+	}
+
+	private class NumDesc{
+		NumberStyle numberStyle;
+		int start,end;
 
-	// On the left hand side of the road.
-	private NumberStyle leftNumberStyle;
-	private int leftStart;
-	private int leftEnd;
+		public NumDesc(NumberStyle numberStyle, int start, int end) {
+			this.numberStyle = numberStyle;
+			this.start = start;
+			this.end = end;
+		}
+		public boolean contained(int hn){
+			boolean isEven = (hn % 2 == 0);
+			if (numberStyle == NumberStyle.BOTH
+					|| numberStyle == NumberStyle.EVEN && isEven
+					|| numberStyle == NumberStyle.ODD && !isEven){
+				if (start <= end) {
+					if (start <= hn && hn <= end)
+						return true;
+				}
+				else { 
+					if (end <= hn && hn <= start)
+						return true;
+				}
+			}
+			return false;
+		}
 
-	// On the right hand side of the road.
-	private NumberStyle rightNumberStyle;
-	private int rightStart;
-	private int rightEnd;
+		@Override
+		public String toString() {
+			return String.format("%s,%d,%d", numberStyle, start,end);
+		}
+
+	}
 
 	public Numbers() {
 	}
 
 	/**
 	 * This constructor takes a comma separated list as in the polish format. Also used in testing as
-	 * it is an easy way to set all the parameters at once.
+	 * it is an easy way to set all common parameters at once.
 	 *
 	 * @param spec Node number, followed by left and then right parameters as in the polish format.
 	 */
 	public Numbers(String spec) {
 		String[] strings = spec.split(",");
 		nodeNumber = Integer.valueOf(strings[0]);
-		leftNumberStyle = NumberStyle.fromChar(strings[1]);
-		leftStart = Integer.valueOf(strings[2]);
-		leftEnd = Integer.valueOf(strings[3]);
-		rightNumberStyle = NumberStyle.fromChar(strings[4]);
-		rightStart = Integer.valueOf(strings[5]);
-		rightEnd = Integer.valueOf(strings[6]);
-	}
+		NumberStyle numberStyle = NumberStyle.fromChar(strings[1]);
+		int start = Integer.valueOf(strings[2]);
+		int end = Integer.valueOf(strings[3]);
+		setNumbers(LEFT, numberStyle, start, end);
+		numberStyle = NumberStyle.fromChar(strings[4]);
+		start = Integer.valueOf(strings[5]);
+		end = Integer.valueOf(strings[6]);
+		setNumbers(RIGHT, numberStyle, start, end);
 
-	public int getNodeNumber() {
-		return nodeNumber;
+		if (strings.length > 8){
+			// zip codes 
+			String zip = strings[7];
+			if ("-1".equals(zip) == false)
+				setZipCode(LEFT, new ZipCodeInfo(zip));
+			zip = strings[8];
+			if ("-1".equals(zip) == false)
+				setZipCode(RIGHT, new ZipCodeInfo(zip));
+		}
+		if (strings.length > 9){
+			String city,region,country;
+			int nextPos = 9;
+			city = strings[nextPos];
+			if ("-1".equals(city) == false){
+				region = strings[nextPos + 1];
+				country = strings[nextPos + 2];
+				setCityInfo(LEFT, new CityInfo(city, region, country));
+				nextPos = 12;
+			} else 
+				nextPos = 10;
+			city = strings[nextPos];
+			if ("-1".equals(city) == false){
+				region = strings[nextPos + 1];
+				country = strings[nextPos + 2];
+				setCityInfo(RIGHT, new CityInfo(city, region, country));
+			} 			
+		} 		
 	}
 
-	public void setNodeNumber(int nodeNumber) {
-		this.nodeNumber = nodeNumber;
+	public void setNumbers(boolean left, NumberStyle numberStyle, int start, int end){
+		if (numberStyle != NumberStyle.NONE || start != -1 || end != -1){
+			RoadSide rs = assureSideIsAllocated(left);
+			rs.numbers = new NumDesc(numberStyle, start, end);
+		} else {
+			RoadSide rs = (left) ? leftSide : rightSide;
+			if (rs != null)
+				rs.numbers = null;
+			removeIfEmpty(left);
+		}
 	}
 
-	public int getRnodNumber() {
-		if (rnodNumber == null) {
-			log.error("WARNING: rnod not set!!");
-			return nodeNumber;
+	public void setCityInfo(boolean left, CityInfo ci){
+		if (ci != null){
+			RoadSide rs = assureSideIsAllocated(left);
+			rs.cityInfo = ci;
+		} else {
+			RoadSide rs = (left) ? leftSide : rightSide;
+			if (rs != null)
+				rs.cityInfo = null;
+			removeIfEmpty(left);
 		}
-		return rnodNumber;
 	}
 
-	public boolean hasRnodNumber() {
-		return rnodNumber != null;
+	public CityInfo getCityInfo(boolean left){
+		RoadSide rs = (left) ? leftSide : rightSide;
+		return (rs != null) ? rs.cityInfo : null;
 	}
 
-	public void setRnodNumber(int rnodNumber) {
-		this.rnodNumber = rnodNumber;
+	public void setZipCode(boolean left, ZipCodeInfo zipCode){
+		if (zipCode != null){
+			RoadSide rs = assureSideIsAllocated(left);
+			rs.zipCode = zipCode;
+		} else {
+			RoadSide rs = (left) ? leftSide : rightSide;
+			if (rs != null)
+				rs.zipCode= null;
+			removeIfEmpty(left);
+		}
 	}
 
-	public NumberStyle getLeftNumberStyle() {
-		return leftNumberStyle;
+
+	public ZipCodeInfo getZipCodeInfo (boolean left){
+		RoadSide rs = (left) ? leftSide : rightSide;
+		return (rs != null) ? rs.zipCode: null;
 	}
 
-	public void setLeftNumberStyle(NumberStyle leftNumberStyle) {
-		this.leftNumberStyle = leftNumberStyle;
+	private void removeIfEmpty(boolean left){
+		if (left && leftSide != null && leftSide.isEmpty())
+			leftSide = null;
+		if (!left && rightSide != null && rightSide.isEmpty())
+			rightSide = null;
 	}
 
-	public int getLeftStart() {
-		return leftStart;
+	// allocate or return allocated RoadSide instance for the given road side
+	private RoadSide assureSideIsAllocated(boolean left){
+		if (left && leftSide == null)
+			leftSide = new RoadSide();
+		if (!left && rightSide == null)
+			rightSide = new RoadSide();
+		return (left) ? leftSide : rightSide;
 	}
 
-	public void setLeftStart(int leftStart) {
-		this.leftStart = leftStart;
+	public int getNodeNumber() {
+		return nodeNumber;
 	}
 
-	public int getLeftEnd() {
-		return leftEnd;
+	public void setNodeNumber(int nodeNumber) {
+		this.nodeNumber = nodeNumber;
 	}
 
-	public void setLeftEnd(int leftEnd) {
-		this.leftEnd = leftEnd;
+	public int getIndex() {
+		if (indexNumber == null) {
+			log.error("WARNING: index not set!!");
+			return nodeNumber;
+		}
+		return indexNumber;
 	}
 
-	public NumberStyle getRightNumberStyle() {
-		return rightNumberStyle;
+	public boolean hasIndex() {
+		return indexNumber != null;
 	}
 
-	public void setRightNumberStyle(NumberStyle rightNumberStyle) {
-		this.rightNumberStyle = rightNumberStyle;
+	/**
+	 * @param index the nth number node 
+	 */
+	public void setIndex(int index) {
+		this.indexNumber = index;
 	}
 
-	public int getRightStart() {
-		return rightStart;
+	private NumDesc getNumbers(boolean left) {
+		RoadSide rs = (left) ? leftSide : rightSide;
+		return (rs != null) ? rs.numbers : null;  
 	}
 
-	public void setRightStart(int rightStart) {
-		this.rightStart = rightStart;
+	public NumberStyle getNumberStyle(boolean left) {
+		NumDesc n = getNumbers(left);
+		return (n == null) ? NumberStyle.NONE : n.numberStyle;
 	}
 
-	public int getRightEnd() {
-		return rightEnd;
+	public int getStart(boolean left) {
+		NumDesc n = getNumbers(left);
+		return (n == null) ? -1 : n.start; // -1 is the default in the polish format
 	}
 
-	public void setRightEnd(int rightEnd) {
-		this.rightEnd = rightEnd;
+	public int getEnd(boolean left) {
+		NumDesc n = getNumbers(left);
+		return (n == null) ? -1 : n.end; // -1 is the default in the polish format
 	}
 
 	public String toString() {
 		String nodeStr = "0";
 		if (nodeNumber > 0)
 			nodeStr = String.valueOf(nodeNumber);
-		else if (getRnodNumber() > 0)
-			nodeStr = String.format("(n%d)", getRnodNumber());
+		else if (getIndex() > 0)
+			nodeStr = String.format("(n%d)", getIndex());
 
-		return String.format("%s,%s,%d,%d,%s,%d,%d",
+		nodeStr = String.format("%s,%s,%d,%d,%s,%d,%d",
 				nodeStr,
-				leftNumberStyle,
-				leftStart,
-				leftEnd,
-				rightNumberStyle,
-				rightStart,
-				rightEnd);
+				getNumberStyle(LEFT),
+				getStart(LEFT),
+				getEnd(LEFT),
+				getNumberStyle(RIGHT),
+				getStart(RIGHT),
+				getEnd(RIGHT));
+
+		if (getCityInfo(LEFT) != null || getCityInfo(RIGHT) != null
+				|| getZipCodeInfo(LEFT) != null || getZipCodeInfo(RIGHT) != null) {
+			nodeStr = String.format("%s,%s,%s", nodeStr,
+					getPolishZipCode(LEFT), getPolishZipCode(RIGHT));
+			if (getCityInfo(LEFT) != null || getCityInfo(RIGHT) != null) {
+				nodeStr = String.format("%s,%s,%s",nodeStr,
+						getPolishCityInfo(LEFT),getPolishCityInfo(RIGHT)); 
+			}
+		}
+		return nodeStr; 		
 	}
 
+	public NumberStyle getLeftNumberStyle() {
+		return getNumberStyle(LEFT);
+	}
+	public NumberStyle getRightNumberStyle() {
+		return getNumberStyle(RIGHT);
+	}
+	public int getLeftStart(){
+		return getStart(LEFT);
+	}
+	public int getRightStart(){
+		return getStart(RIGHT);
+	}
+	public int getLeftEnd(){
+		return getEnd(LEFT);
+	}
+	public int getRightEnd(){
+		return getEnd(RIGHT);
+	} 
+
 	public boolean equals(Object obj) {
 		if (!(obj instanceof Numbers))
 			return false;
@@ -155,5 +294,131 @@ public class Numbers {
 
 	public int hashCode() {
 		return toString().hashCode();
+	} 
+
+	public boolean isPlausible(){
+		if (!isPlausible(getLeftNumberStyle(), getLeftStart(), getLeftEnd()))
+			return false;
+		if (!isPlausible(getRightNumberStyle(), getRightStart(), getRightEnd()))
+			return false;
+		if (getLeftNumberStyle() == NumberStyle.NONE
+				|| getRightNumberStyle() == NumberStyle.NONE)
+			return true;
+		if (getCityInfo(LEFT) != null){
+			if (getCityInfo(LEFT).equals(getCityInfo(RIGHT)) == false)
+				return true;
+		} else if (getCityInfo(RIGHT) != null)
+			return true;
+		if (getZipCodeInfo(LEFT) != null){
+			if (getZipCodeInfo(LEFT).equals(getZipCodeInfo(RIGHT)) == false)
+				return true;
+		} else if (getCityInfo(RIGHT) != null)
+			return true;
+		if (getLeftNumberStyle() == getRightNumberStyle() || getLeftNumberStyle() == NumberStyle.BOTH || getRightNumberStyle()==NumberStyle.BOTH){
+			// check if intervals are overlapping
+			int start1, start2,end1,end2;
+			if (getLeftStart() < getLeftEnd()){
+				start1 = getLeftStart();
+				end1 = getLeftEnd();
+			} else {
+				start1 = getLeftEnd();
+				end1 = getLeftStart();
+			}
+			if (getRightStart() < getRightEnd()){
+				start2 = getRightStart();
+				end2 = getRightEnd();
+			} else {
+				start2 = getRightEnd();
+				end2 = getRightStart();
+			}
+			if (start2 > end1 || end2 < start1)
+				return true;
+			if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd() && getLeftStart() == getRightStart())
+				return true; // single number on both sides of the road 
+
+			return false;
+		}
+
+		return true;
+	}
+
+	private static boolean isPlausible(NumberStyle style, int start, int end){
+		if (Math.abs(start - end) > MAX_DELTA)
+			return false;
+		if (style == NumberStyle.EVEN)
+			return start % 2 == 0 && end % 2 == 0;
+		if (style == NumberStyle.ODD)
+			return start % 2 != 0 && end % 2 != 0;
+		return true;
+	}
+
+	public boolean isContained(int hn, boolean left){
+		RoadSide rs = left ? leftSide : rightSide;
+		if (rs == null || rs.numbers == null)
+			return false;
+		return rs.numbers.contained(hn);
+	}
+	
+	/**
+	 * @param hn a house number
+	 * @param left left or right side
+	 * @return 0 if the number is not within the intervals, 1 if it is on one side, 2 if it on both sides 
+	 */
+	public int countMatches(int hn) {
+		int matches = 0;
+		if (isContained(hn, LEFT))
+			matches++;
+		if (isContained(hn, RIGHT))
+			matches++;
+		if (matches > 1){
+			if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd())
+				matches = 1; // single number on both sides of the road 
+		}
+		return matches;
+	}
+
+	/** 
+	 * Compare all fields that describe the interval, but not the position
+	 * @param other
+	 * @return true if these fields are equal
+	 */
+	public boolean isSimilar(Numbers other){
+		if (other == null)
+			return false;
+		if (getLeftNumberStyle() != other.getLeftNumberStyle()
+				|| getLeftStart() != other.getLeftStart() || getLeftEnd() != other.getLeftEnd()
+				|| getRightNumberStyle() != other.getRightNumberStyle()
+				|| getRightStart() != other.getRightStart() || getRightEnd() != other.getRightEnd())
+			return false;
+		return true;
+
 	}
+
+	public boolean isEmpty(){
+		return getLeftNumberStyle() == NumberStyle.NONE && getRightNumberStyle() == NumberStyle.NONE;
+	}
+
+
+	private String getPolishCityInfo (boolean left){
+		CityInfo ci = getCityInfo(left);
+		if (ci == null)
+			return "-1";
+		StringBuilder sb = new StringBuilder();
+		if (ci.getCity() != null)
+			sb.append(ci.getCity());
+		sb.append(",");
+		if (ci.getRegion() != null)
+			sb.append(ci.getRegion());
+		sb.append(",");
+		if (ci.getCountry() != null)
+			sb.append(ci.getCountry());
+		return sb.toString();
+	}
+
+	private String getPolishZipCode (boolean left){
+		ZipCodeInfo zip = getZipCodeInfo(left);
+		return (zip != null && zip.getZipCode() != null ) ? zip.getZipCode() : "-1";
+	}
+
+
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
index 95e5a4f..cc38c9e 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
@@ -16,7 +16,10 @@
  */
 package uk.me.parabola.imgfmt.app.net;
 
+import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -31,6 +34,8 @@ import uk.me.parabola.imgfmt.app.lbl.City;
 import uk.me.parabola.imgfmt.app.lbl.Zip;
 import uk.me.parabola.imgfmt.app.trergn.Polyline;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.CityInfo;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
 
 /**
  * A road definition.  This ties together all segments of a single road
@@ -83,6 +88,9 @@ public class RoadDef {
 	private static final int TABAACCESS_FLAG_NO_BIKE    = 0x0020;
 	private static final int TABAACCESS_FLAG_NO_TRUCK   = 0x0040;
 	
+	// true if road should not be added to NOD 
+	private boolean skipAddToNOD;
+	
 	// the offset in Nod2 of our Nod2 record
 	private int offsetNod2;
 
@@ -109,8 +117,6 @@ public class RoadDef {
 
 	private final SortedMap<Integer,List<RoadIndex>> roadIndexes = new TreeMap<>();
 
-	private City city;
-	private Zip zip;
 	private boolean paved = true;
 	private boolean ferry;
 	private boolean roundabout;
@@ -120,6 +126,8 @@ public class RoadDef {
 	private Set<String> messageIssued;
 
 	private final List<Offset> rgnOffsets = new ArrayList<>();
+	// for the NOD2 bit stream   
+	private BitSet nod2BitSet;
 
 	/*
 	 * Everything that's relevant for writing out Nod 2.
@@ -145,6 +153,8 @@ public class RoadDef {
 	private final long id;
 	private final String name;
 	private List<Numbers> numbersList;
+	private List<City> cityList;
+	private List<Zip> zipList;
 	private int nodeCount;
 
 	public RoadDef(long id, String name) {
@@ -196,14 +206,13 @@ public class RoadDef {
 		if (numlabels == 0)
 			return;
 		assert numlabels > 0;
-
+		Zip zip = getZips().isEmpty() ? null : getZips().get(0);
+		City city = getCities().isEmpty() ? null: getCities().get(0);
 		offsetNet1 = writer.position();
-
 		NumberPreparer numbers = null;
 		if (numbersList != null) {
-			numbers = new NumberPreparer(numbersList);
-			numbers.fetchBitStream();
-			if (!numbers.isValid()){
+			numbers = new NumberPreparer(numbersList, zip, city, numCities, numZips);
+			if (!numbers.prepare()){
 				numbers = null;
 				log.warn("Invalid housenumbers in",this.toString());
 			}
@@ -222,33 +231,72 @@ public class RoadDef {
 
 		if((netFlags & NET_FLAG_ADDRINFO) != 0) {
 			nodeCount--;
+			if (nodeCount + 2 != nnodes){
+				log.error("internal error? The nodeCount doesn't match value calculated by RoadNetWork:",this);
+			}
 			writer.put((byte) (nodeCount & 0xff)); // lo bits of node count
 
-			int code = 0xe8;     // zip and city present
-			code |= ((nodeCount >> 8) & 0x3); // top bits of node count
-			if(city == null)
-				code |= 0x10; // no city
-			if(zip == null)
-				code |= 0x04; // no zip
-			if (numbers != null) {
-				code &= ~0xc0;
-				if (numbers.fetchBitStream().getLength() > 255)
-					code |= 0x40;
-			}
+			int code = ((nodeCount >> 8) & 0x3); // top bits of node count
+			int len, flag;
+			
+			ByteArrayOutputStream zipBuf = null, cityBuf = null;
+			len = (numbers == null)  ? 0: numbers.zipWriter.getBuffer().size();
+			if (len > 0){
+				zipBuf = numbers.zipWriter.getBuffer();
+				flag = (len > 255) ? 1 : 0;
+			} else 
+				flag = (zip == null) ? 3 : 2;
+			code |= flag << 2;
+			
+			len = (numbers == null)  ? 0: numbers.cityWriter.getBuffer().size();
+			if (len > 0){
+				cityBuf = numbers.cityWriter.getBuffer();
+				flag = (len > 255) ? 1 : 0;
+			} else 
+				flag = (city == null) ? 3 : 2;
+			code |= flag << 4;
+			
+			len = (numbers == null) ? 0 : numbers.fetchBitStream().getLength();
+			if (len > 0){
+				flag = (len > 255) ? 1 : 0;
+			} else 
+				flag = 3;
+			code |= flag << 6;
+			
 			writer.put((byte)code);
-			if(zip != null) {
-				char zipIndex = (char)zip.getIndex();
-				if(numZips > 255)
-					writer.putChar(zipIndex);
+//			System.out.printf("%d %d %d\n", (code >> 2 & 0x3), (code >> 4 & 0x3), (code >> 6 & 0x3));  
+			
+			if (zipBuf != null){
+				len = zipBuf.size();
+				if (len > 255)
+					writer.putChar((char) len);
 				else
-					writer.put((byte)zipIndex);
+					writer.put((byte) len);
+				writer.put(zipBuf.toByteArray());
+			} else {
+				if(zip != null) {
+					char zipIndex = (char)zip.getIndex();
+					if(numZips > 255)
+						writer.putChar(zipIndex);
+					else
+						writer.put((byte)zipIndex);
+				}
 			}
-			if(city != null) {
-				char cityIndex = (char)city.getIndex();
-				if(numCities > 255)
-					writer.putChar(cityIndex);
+			if (cityBuf != null){
+				len = cityBuf.size();
+				if (len > 255)
+					writer.putChar((char) len);
 				else
-					writer.put((byte)cityIndex);
+					writer.put((byte) len);
+				writer.put(cityBuf.toByteArray());
+			} else {
+				if(city != null) {
+					char cityIndex = (char)city.getIndex();
+					if(numCities > 255)
+						writer.putChar(cityIndex);
+					else
+						writer.put((byte)cityIndex);
+				}
 			}
 			if (numbers != null) {
 				BitWriter bw = numbers.fetchBitStream();
@@ -356,7 +404,7 @@ public class RoadDef {
 		l.add(new RoadIndex(pl));
 
 		if (level == 0) {
-			nodeCount += pl.getNodeCount();
+			nodeCount += pl.getNodeCount(hasHouseNumbers());
 		}
 	}
 
@@ -449,11 +497,11 @@ public class RoadDef {
 		}
 	}
 
-	private boolean internalNodes = true;
+	private boolean internalNodes;
 
 	/**
 	 * Does the road have any nodes besides start and end?
-	 *
+	 * These can be number nodes or routing nodes.
 	 * This affects whether we need to write extra bits in
 	 * the bitstream in RGN.
 	 */
@@ -472,6 +520,8 @@ public class RoadDef {
 	 * which will be pointed at from NET 1.
 	 */
 	public void setNode(RouteNode node) {
+		if (skipAddToNOD)
+			return;
 		netFlags |= NET_FLAG_NODINFO;
 		this.node = node;
 	}
@@ -498,6 +548,11 @@ public class RoadDef {
 			netFlags |= NET_FLAG_ADDRINFO;
 		}
 	}
+	
+	public List<Numbers> getNumbersList() {
+		return numbersList;
+	}
+
 
 	/**
 	 * Write this road's NOD2 entry.
@@ -510,6 +565,11 @@ public class RoadDef {
 	public void writeNod2(ImgFileWriter writer) {
 		if (!hasNodInfo())
 			return;
+		if (skipAddToNOD){
+			// should not happen
+			log.error("internal error: writeNod2 called for roaddef with skipAddToNOD=true");
+			return;
+		}
 
 		log.debug("writing nod2");
 
@@ -521,19 +581,27 @@ public class RoadDef {
 		// this is related to the number of nodes, but there
 		// is more to it...
 		// For now, shift by one if the first node is not a
-		// routing node. Supposedly, other holes are also
-		// possible.
-		// This might be unnecessary if we just make sure
-		// that every road starts with a node.
+		// routing node.
+		// If the road has house numbers, we count also
+		// the number nodes, and these get a 0 in the bit stream. 
 		int nbits = nnodes;
 		if (!startsWithNode)
 			nbits++;
 		writer.putChar((char) nbits);
 		boolean[] bits = new boolean[nbits];
-		for (int i = 0; i < bits.length; i++)
-			bits[i] = true;
-		if (!startsWithNode)
-			bits[0] = false;
+		
+		if (hasHouseNumbers()){
+			int off = startsWithNode ? 0 :1;
+			for (int i = 0; i < bits.length; i++){
+				if (nod2BitSet.get(i))
+					bits[i+off] = true;
+			}
+		} else { 
+			for (int i = 0; i < bits.length; i++)
+				bits[i] = true;
+			if (!startsWithNode)
+				bits[0] = false;
+		}
 		for (int i = 0; i < bits.length; i += 8) {
 			int b = 0;
             for (int j = 0; j < 8 && j < bits.length - i; j++)
@@ -636,6 +704,7 @@ public class RoadDef {
 
 	private int roadClass = -1;
 
+
 	// road class that goes in various places (really?)
 	public void setRoadClass(int roadClass) {
 		assert roadClass < 0x08;
@@ -679,20 +748,45 @@ public class RoadDef {
 		return (netFlags & NET_FLAG_ONEWAY) != 0;
 	}
 
-	public void setCity(City city) {
-		this.city = city;
+	public void addCityIfNotPresent(City city) {
+		if (city == null){
+			log.error("trying to add null value to city list in road",this);
+			return;
+		}
 		netFlags |= NET_FLAG_ADDRINFO;
+		if (cityList == null){
+			cityList = new ArrayList<>(2);
+		}
+		if (cityList.contains(city) == false)
+			cityList.add(city);
 	}
 
-	public void setZip(Zip zip) {
-		this.zip = zip;
+	public void addZipIfNotPresent(Zip zip) {
+		if (zip == null){
+			log.error("trying to add null value to zip list in road",this);
+			return;
+		}
 		netFlags |= NET_FLAG_ADDRINFO;
+		if (zipList == null){
+			zipList = new ArrayList<>(2);
+		}
+		if (zipList.contains(zip) == false)
+			zipList.add(zip);
 	}
 
-	public City getCity() {
-		return city;
+	
+	public List<City> getCities(){
+		if (cityList == null)
+			return Collections.emptyList();
+		return cityList;
 	}
 
+	public List<Zip> getZips(){
+		if (zipList == null)
+			return Collections.emptyList();
+		return zipList;
+	}
+	
 	public boolean paved() {
 		return paved;
 	}
@@ -749,4 +843,36 @@ public class RoadDef {
 		return previouslyIssued;
 	}
 
+	public void setNod2BitSet(BitSet bs) {
+		if (skipAddToNOD)
+			return;
+		nod2BitSet = bs;
+	}
+
+	public boolean skipAddToNOD() {
+		return skipAddToNOD;
+	}
+
+	public void skipAddToNOD(boolean skip) {
+		this.skipAddToNOD = skip;
+	}
+
+	public void resetImgData() {
+		zipList = null;
+		cityList = null;
+		if (numbersList != null){
+			for (Numbers num : numbersList){
+				for (int side = 0; side < 2; side++){
+					boolean left = side == 0;
+					CityInfo ci = num.getCityInfo(left);
+					if (ci != null)
+						ci.setImgCity(null);
+					ZipCodeInfo z = num.getZipCodeInfo(left);
+					if (z != null)
+						z.setImgZip(null);
+				}
+			}
+		}
+	}
+	
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java b/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java
index 1b2bf9d..cad0bcb 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java
@@ -18,6 +18,7 @@ package uk.me.parabola.imgfmt.app.net;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -79,17 +80,34 @@ public class RoadNetwork {
 		int pointsHash = 0;
 
 		int npoints = coordList.size();
+		int numCoordNodes = 0;
+		boolean hasInternalNodes = false;
+		int numNumberNodes = 0;
+		BitSet nodeFlags = new BitSet();
 		for (int index = 0; index < npoints; index++) {
 			Coord co = coordList.get(index);
+			int id = co.getId();
 
-			if (index > 0) {
+			if (id != 0){
+				nodeFlags.set(numNumberNodes);
+				++numCoordNodes;
+				if(index > 0 && index < npoints - 1)
+					hasInternalNodes = true;
+			}
+			if (co.isNumberNode())
+				++numNumberNodes;
+			if (index == 0){
+				if (id == 0)
+					roadDef.setStartsWithNode(false);
+				
+			} else { 
 				double d = co.distance(coordList.get(index-1));
 				arcLength += d;
 				roadLength += d;
 			}
-
-			int id = co.getId();
-
+			if (roadDef.skipAddToNOD())
+				continue;
+			
 			pointsHash += co.hashCode();
 
 			if (id == 0)
@@ -194,6 +212,17 @@ public class RoadNetwork {
 			arcLength = 0;
 			pointsHash = co.hashCode();
 		}
+		if (roadDef.hasHouseNumbers()){
+			// we ignore number nodes when we have no house numbers
+			if (numCoordNodes < numNumberNodes)
+				hasInternalNodes = true;
+			roadDef.setNumNodes(numNumberNodes);
+			roadDef.setNod2BitSet(nodeFlags);
+		} else {
+			roadDef.setNumNodes(numCoordNodes);
+		}
+		if (hasInternalNodes)
+			roadDef.setInternalNodes(true);
 		roadDef.setLength(roadLength);
 	}
 
@@ -265,6 +294,8 @@ public class RoadNetwork {
 		long t1 = System.currentTimeMillis();
 		
 		for (RoadDef rd: roadDefs){
+			if (rd.skipAddToNOD())
+				continue;
 			if (rd.getRoadClass() >= 1)
 				rd.getNode().addArcsToMajorRoads(rd);
 		}
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java b/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java
index 4319412..bbdd51a 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java
@@ -47,6 +47,8 @@ public class LinePreparer {
 	private int[] deltas;
 	private boolean[] nodes;
 
+	private boolean ignoreNumberOnlyNodes;
+
 	LinePreparer(Polyline line) {
 		if (line.isRoad() && 
 			line.getSubdiv().getZoom().getLevel() == 0 &&
@@ -55,6 +57,8 @@ public class LinePreparer {
 			// but who knows
 			extraBit = true;
 		}
+		if (!line.hasHouseNumbers())
+			ignoreNumberOnlyNodes = true;
 
 		extTypeLine = line.hasExtendedType();
 
@@ -237,21 +241,23 @@ public class LinePreparer {
 			assert (dy == 0 && lat != lastLat) == false: ("delta lat too large: " +  (lat - lastLat));
 			lastLong = lon;
 			lastLat = lat;
-
-			if (dx != 0 || dy != 0 || (extraBit && co.getId() != 0))
+			boolean isSpecialNode = false;
+			if (co.getId() > 0 || (co.isNumberNode() && ignoreNumberOnlyNodes == false))
+				isSpecialNode = true;
+			if (dx != 0 || dy != 0 || extraBit && isSpecialNode)
 				firstsame = i;
 
 			/*
 			 * Current thought is that the node indicator is set when
-			 * the point is a node. There's a separate first extra bit
+			 * the point is a routing node or a house number node. 
+			 * There's a separate first extra bit
 			 * that always appears to be false. The last points' extra bit
 			 * is set if the point is a node and this is not the last
 			 * polyline making up the road.
-			 * Todo: special case the last bit
 			 */
 			if (extraBit) {
 				boolean extra = false;
-				if (co.getId() != 0) {
+				if (isSpecialNode) {
 					if (i < nodes.length - 1)
 						// inner node of polyline
 						extra = true;
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
index 8b6c2d4..e8f9ce4 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
@@ -278,13 +278,25 @@ public class Polyline extends MapObject {
 		return getSubdiv().getLongitude() + (getDeltaLong() << getSubdiv().getShift());
 	}
 
-	public int getNodeCount() {
+	/**
+	 * 
+	 * @param countAllNodes : false: count only coord nodes, true: count number nodes
+	 * @return
+	 */
+	public int getNodeCount(boolean countAllNodes ) {
 		int idx = 0;
 		int count = 0;
+		
 		for (Coord co : points) {
-			if (idx++ > 0 && co.getId() > 0)
+			if (idx++ > 0 && (co.getId() > 0 || countAllNodes && co.isNumberNode()))
 				count++;
 		}
 		return count;
 	}
+
+	public boolean hasHouseNumbers() {
+		if (!isRoad())
+			return false;
+		return roaddef.hasHouseNumbers();
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/CommandArgsReader.java b/src/uk/me/parabola/mkgmap/CommandArgsReader.java
index 9d5fd60..540a62c 100644
--- a/src/uk/me/parabola/mkgmap/CommandArgsReader.java
+++ b/src/uk/me/parabola/mkgmap/CommandArgsReader.java
@@ -184,8 +184,10 @@ public class CommandArgsReader {
 
 		switch (option) {
 		case "input-file":
-			log.debug("adding filename", value);
-			add(new Filename(value));
+			if (value != null){
+				log.debug("adding filename", value);
+				add(new Filename(value));
+			}
 			break;
 		case "read-config":
 			readConfigFile(value);
diff --git a/src/uk/me/parabola/mkgmap/build/Locator.java b/src/uk/me/parabola/mkgmap/build/Locator.java
index cf21504..076a800 100644
--- a/src/uk/me/parabola/mkgmap/build/Locator.java
+++ b/src/uk/me/parabola/mkgmap/build/Locator.java
@@ -20,9 +20,9 @@ import java.util.Set;
 
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.MapPoint;
-import uk.me.parabola.mkgmap.general.MapPointKdTree;
 import uk.me.parabola.mkgmap.reader.osm.Tags;
 import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.KdTree;
 import uk.me.parabola.util.MultiHashMap;
 
 public class Locator {
@@ -31,7 +31,7 @@ public class Locator {
     /** hash map to collect equally named MapPoints*/ 
 	private final MultiHashMap<String, MapPoint> cityMap = new MultiHashMap<String, MapPoint>();
 	
-	private final MapPointKdTree cityFinder = new MapPointKdTree();
+	private final KdTree<MapPoint> cityFinder = new KdTree<>();
 	private final List<MapPoint> placesMap  =  new ArrayList<MapPoint>();
 
 	/** Contains the tags defined by the option name-tag-list */
diff --git a/src/uk/me/parabola/mkgmap/build/LocatorUtil.java b/src/uk/me/parabola/mkgmap/build/LocatorUtil.java
index 3bb11b7..5392375 100644
--- a/src/uk/me/parabola/mkgmap/build/LocatorUtil.java
+++ b/src/uk/me/parabola/mkgmap/build/LocatorUtil.java
@@ -69,6 +69,12 @@ public class LocatorUtil {
 			autofillOptions.add("nearest");
 			autofillOptions.remove("3");
 		}	
+		final List<String> knownOptions = Arrays.asList("bounds","is_in","nearest");
+		for (String s : autofillOptions){
+			if (knownOptions.contains(s) == false){
+				throw new IllegalArgumentException(s + " is not a known sub option for option location-autofill: " + optionStr);
+			}
+		}
 		return autofillOptions;
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/build/MapBuilder.java b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
index 67f5d6d..ed482e0 100644
--- a/src/uk/me/parabola/mkgmap/build/MapBuilder.java
+++ b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
@@ -40,7 +40,9 @@ import uk.me.parabola.imgfmt.app.lbl.Zip;
 import uk.me.parabola.imgfmt.app.map.Map;
 import uk.me.parabola.imgfmt.app.net.NETFile;
 import uk.me.parabola.imgfmt.app.net.NODFile;
+import uk.me.parabola.imgfmt.app.net.Numbers;
 import uk.me.parabola.imgfmt.app.net.RoadDef;
+import uk.me.parabola.imgfmt.app.net.RoadNetwork;
 import uk.me.parabola.imgfmt.app.net.RouteCenter;
 import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
@@ -73,6 +75,7 @@ import uk.me.parabola.mkgmap.filters.RemoveObsoletePointsFilter;
 import uk.me.parabola.mkgmap.filters.RoundCoordsFilter;
 import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
 import uk.me.parabola.mkgmap.filters.SizeFilter;
+import uk.me.parabola.mkgmap.general.CityInfo;
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
 import uk.me.parabola.mkgmap.general.MapDataSource;
@@ -82,7 +85,7 @@ import uk.me.parabola.mkgmap.general.MapLine;
 import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapRoad;
 import uk.me.parabola.mkgmap.general.MapShape;
-import uk.me.parabola.imgfmt.app.net.RoadNetwork;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
 import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource;
 import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
 import uk.me.parabola.util.Configurable;
@@ -401,34 +404,70 @@ public class MapBuilder implements Configurable {
 					}
 				}
 
-				if (cityName == null && (cityCountryName != null || cityRegionName != null)) {
-					// if city name is unknown and region and/or country is known 
-					// use empty name for the city
-					cityName = UNKNOWN_CITY_NAME;
-				}
-				
-				if(cityName != null) {
-
-					Country cc = (cityCountryName == null)? getDefaultCountry() : lbl.createCountry(cityCountryName, locator.getCountryISOCode(cityCountryName));
-
-					Region cr = (cityRegionName == null)? getDefaultRegion(cc) : lbl.createRegion(cc, cityRegionName, null);
+				MapRoad road = (MapRoad) line;
+				road.resetImgData();
 
-					if(cr != null) {
-						((MapRoad)line).setRoadCity(lbl.createCity(cr, cityName, false));
-					}
-					else {
-						((MapRoad)line).setRoadCity(lbl.createCity(cc, cityName, false));
-					}
-				}
+				City roadCity = calcCity(lbl, cityName, cityRegionName, cityCountryName);
+				if (roadCity != null)
+					road.addRoadCity(roadCity);
 
 				if(zipStr != null) {
-					((MapRoad)line).setRoadZip(lbl.createZip(zipStr));
+					road.addRoadZip(lbl.createZip(zipStr));
 				}
 
+				List<Numbers> numbers = road.getRoadDef().getNumbersList();
+				if (numbers != null){
+					for (Numbers num : numbers){
+						for (int i = 0; i < 2; i++){
+							boolean left = (i == 0);
+							ZipCodeInfo zipInfo = num.getZipCodeInfo(left);
+							if (zipInfo != null && zipInfo.getZipCode() != null){
+								Zip zip = zipInfo.getImgZip();
+								if (zipInfo.getImgZip() == null){
+									zip = lbl.createZip(zipInfo.getZipCode());
+									zipInfo.setImgZip(zip);
+								}
+								if (zip != null)
+									road.addRoadZip(zip);
+							}
+							CityInfo cityInfo = num.getCityInfo(left);
+							if (cityInfo != null){
+								City city = cityInfo.getImgCity();
+								if (city == null ){
+									city = calcCity(lbl, cityInfo.getCity(), cityInfo.getRegion(), cityInfo.getCountry());
+									cityInfo.setImgCity(city);
+								}
+								if (city != null)
+									road.addRoadCity(city);
+							}
+						}
+					}
+				}
 			}
 		}	
 	}
 
+	private City calcCity(LBLFile lbl, String city, String region, String country){
+		if (city == null && region == null && country == null)
+			return null;
+		Country cc = (country == null)? getDefaultCountry() : lbl.createCountry(country, locator.getCountryISOCode(country));
+		Region cr = (region == null)? getDefaultRegion(cc) : lbl.createRegion(cc, region, null);
+		if (city == null && (country != null || region != null)) {
+			// if city name is unknown and region and/or country is known 
+			// use empty name for the city
+			city = UNKNOWN_CITY_NAME;
+		}
+		if (city == null)
+			return null;
+		if(cr != null) {
+			return lbl.createCity(cr, city, false);
+		}
+		else {
+			return lbl.createCity(cc, city, false);
+		}
+	}
+	
+	
 	private void processPOIs(Map map, MapDataSource src) {
 
 		LBLFile lbl = map.getLblFile();
@@ -443,7 +482,7 @@ public class MapBuilder implements Configurable {
 			// * cities (already processed)
 			// * extended types (address information not shown in MapSource and on GPS)
 			// * all POIs except roads in case the no-poi-address option is set
-			else if (!p.isCity() && !p.hasExtendedType() && (p.isRoadNamePOI() || poiAddresses))
+			else if (!p.isCity() && !p.hasExtendedType() &&  poiAddresses)
 			{
 				
 				String countryStr = p.getCountry();
@@ -488,44 +527,10 @@ public class MapBuilder implements Configurable {
 				}
 
 
-				if(p.isRoadNamePOI() && cityStr != null)
-				{
-					// If it is road POI add city name and street name into address info
-					p.setStreet(p.getName());
-					p.setName(p.getName() + "/" + cityStr);
-				}
-
 				POIRecord r = lbl.createPOI(p.getName());	
-
-				if (cityStr == null && (countryStr != null || regionStr != null)) {
-					// if city name is unknown and region and/or country is known 
-					// use empty name for the city
-					cityStr = UNKNOWN_CITY_NAME;
-				}
 				
-				if(cityStr != null)
-				{
-					Country thisCountry;
-
-					if(countryStr != null)
-						thisCountry = lbl.createCountry(countryStr, locator.getCountryISOCode(countryStr));
-					else
-						thisCountry = getDefaultCountry();
-
-					Region thisRegion;
-					if(regionStr != null)
-						thisRegion = lbl.createRegion(thisCountry,regionStr, null);
-					else
-						thisRegion = getDefaultRegion(thisCountry);
-
-					City city;
-					if(thisRegion != null)
-						city = lbl.createCity(thisRegion, cityStr, false);
-					else
-						city = lbl.createCity(thisCountry, cityStr, false);
-
-					r.setCity(city);
-
+				if(cityStr != null || regionStr != null || countryStr != null){
+					r.setCity(calcCity(lbl, cityStr, regionStr, countryStr));
 				}
 
 				if (zipStr != null)
diff --git a/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java b/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
index 487cfc4..29e06c6 100644
--- a/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
@@ -310,15 +310,19 @@ public class MdrBuilder implements Combiner {
 			String name = road.getName();
 			if (name == null || name.isEmpty())
 				continue;
-
 			Mdr5Record mdrCity = null;
-			if (road.getCity() != null) {
-				mdrCity = cityList.get(road.getCity().getIndex() - 1);
-				if (mdrCity.getMapIndex() == 0)
-					mdrCity = null;
+			List<City> cities = road.getCities();
+			if (cities.isEmpty())
+				mdrFile.addStreet(road, mdrCity);
+			else {
+				for (City city : cities){
+					mdrCity = cityList.get(city.getIndex() - 1);
+					if (mdrCity.getMapIndex() == 0)
+						mdrCity = null;
+
+					mdrFile.addStreet(road, mdrCity);
+				}
 			}
-			
-			mdrFile.addStreet(road, mdrCity);
 		}
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java b/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
index f2c60f9..1063010 100644
--- a/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
@@ -86,7 +86,7 @@ public class LinePreparerFilter implements MapFilter {
 			lastLong = lon;
 			lastLat = lat;
 			if (dx == 0 && dy == 0){
-				if(!line.isRoad() || co.getId() == 0)
+				if(!line.isRoad() || (co.getId() == 0 && co.isNumberNode() == false))
 					continue;
 			}
 			++numPointsEncoded;
diff --git a/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java b/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java
index 3d7b879..00cfae8 100644
--- a/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java
@@ -36,7 +36,7 @@ import uk.me.parabola.mkgmap.general.MapShape;
 public class LineSizeSplitterFilter implements MapFilter {
 	private static final Logger log = Logger.getLogger(LineSizeSplitterFilter.class);
 
-	private static final int MAX_SIZE = 0x7fff;
+	public static final int MAX_SIZE = 0x7fff;
 
 	private int maxSize;
 
diff --git a/src/uk/me/parabola/mkgmap/general/CityInfo.java b/src/uk/me/parabola/mkgmap/general/CityInfo.java
new file mode 100644
index 0000000..70d51b2
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/general/CityInfo.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+package uk.me.parabola.mkgmap.general;
+
+import uk.me.parabola.imgfmt.app.lbl.City;
+
+public class CityInfo implements Comparable<CityInfo> {
+	private static final String UNKNOWN = "?";
+	private final String city,region,country;
+	private City imgCity;
+
+	public CityInfo (String city, String region, String country){
+		this.city = (city != null) ? city: UNKNOWN;
+		this.region = (region != null) ?  region : UNKNOWN;
+		this.country = (country != null) ? country : UNKNOWN;
+	}
+	
+
+	public String getCity() {
+		if (city == UNKNOWN)
+			return null;
+		return city;
+	}
+
+	public String getRegion() {
+		if (region == UNKNOWN)
+			return null;
+		return region;
+	}
+
+	public String getCountry() {
+		if (country == UNKNOWN)
+			return null;
+		return country;
+	}
+	
+	
+	public City getImgCity() {
+		return imgCity;
+	}
+
+
+	public void setImgCity(City imgCity) {
+		this.imgCity = imgCity;
+	}
+
+	public boolean isEmpty(){
+		return city == UNKNOWN && region == UNKNOWN && country == UNKNOWN;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((city == null) ? 0 : city.hashCode());
+		result = prime * result + ((country == null) ? 0 : country.hashCode());
+		result = prime * result + ((region == null) ? 0 : region.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (!(obj instanceof CityInfo))
+			return false;
+		CityInfo other = (CityInfo) obj;
+		if (city == null) {
+			if (other.city != null)
+				return false;
+		} else if (!city.equals(other.city))
+			return false;
+		if (country == null) {
+			if (other.country != null)
+				return false;
+		} else if (!country.equals(other.country))
+			return false;
+		if (region == null) {
+			if (other.region != null)
+				return false;
+		} else if (!region.equals(other.region))
+			return false;
+		return true;
+	}
+
+	@Override
+	public int compareTo(CityInfo o) {
+		if (this == o)
+			return 0;
+		int d = city.compareTo(o.city);
+		if (d != 0)
+			return d;
+		d = region.compareTo(o.region);
+		if (d != 0)
+			return d;
+		return country.compareTo(o.country);
+	}
+
+	@Override
+	public String toString() {
+		return city + "/" + region + " in " + country; 
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/general/MapDetails.java b/src/uk/me/parabola/mkgmap/general/MapDetails.java
index 3b69f3f..337951a 100644
--- a/src/uk/me/parabola/mkgmap/general/MapDetails.java
+++ b/src/uk/me/parabola/mkgmap/general/MapDetails.java
@@ -47,10 +47,10 @@ public class MapDetails implements MapCollector, MapDataSource {
 	private final List<MapShape> shapes = new ArrayList<MapShape>();
 	private final List<MapPoint> points = new ArrayList<MapPoint>();
 
-	private int minLat = Utils.toMapUnit(180.0);
-	private int minLon = Utils.toMapUnit(180.0);
-	private int maxLat = Utils.toMapUnit(-180.0);
-	private int maxLon = Utils.toMapUnit(-180.0);
+	private int minLat30 = Utils.toMapUnit(180.0) << Coord.DELTA_SHIFT ;
+	private int minLon30 = Utils.toMapUnit(180.0) << Coord.DELTA_SHIFT;
+	private int maxLat30 = Utils.toMapUnit(-180.0) << Coord.DELTA_SHIFT;
+	private int maxLon30 = Utils.toMapUnit(-180.0) << Coord.DELTA_SHIFT;
 
 	// Keep lists of all items that were used.
 	private final Map<Integer, Integer> pointOverviews = new HashMap<Integer, Integer>();
@@ -141,16 +141,17 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 * @param p The coordinates of the point to add.
 	 */
 	public void addToBounds(Coord p) {
-		int lat = p.getLatitude();
-		int lon = p.getLongitude();
-		if (lat < minLat)
-			minLat = lat;
-		if (lat > maxLat)
-			maxLat = lat;
-		if (lon < minLon)
-			minLon = lon;
-		if (lon > maxLon)
-			maxLon = lon;
+		int lat30 = p.getHighPrecLat(); 
+		int lon30 = p.getHighPrecLon();
+		
+		if (lat30 < minLat30)
+			minLat30 = lat30;
+		if (lat30 > maxLat30)
+			maxLat30 = lat30;
+		if (lon30 < minLon30)
+			minLon30 = lon30;
+		if (lon30 > maxLon30)
+			maxLon30 = lon30;
 	}
 
 	/**
@@ -159,6 +160,14 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 * @return An area covering all the points in the map.
 	 */
 	public Area getBounds() {
+		int minLat = minLat30 >> Coord.DELTA_SHIFT;
+		int maxLat = maxLat30 >> Coord.DELTA_SHIFT;
+		int minLon = minLon30 >> Coord.DELTA_SHIFT;
+		int maxLon = maxLon30 >> Coord.DELTA_SHIFT;
+		if ((maxLat << Coord.DELTA_SHIFT) < maxLat30)
+			maxLat++;
+		if ((maxLon << Coord.DELTA_SHIFT) < maxLon30)
+			maxLon++;
 		return new Area(minLat, minLon, maxLat, maxLon);
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/general/MapElement.java b/src/uk/me/parabola/mkgmap/general/MapElement.java
index fad3240..ae6db3e 100644
--- a/src/uk/me/parabola/mkgmap/general/MapElement.java
+++ b/src/uk/me/parabola/mkgmap/general/MapElement.java
@@ -28,7 +28,7 @@ import uk.me.parabola.imgfmt.app.trergn.MapObject;
  * @author Steve Ratcliffe.
  */
 public abstract class MapElement {
-	private String[] labels;
+	protected String[] labels;
 	private int type;
 
 	private int minResolution = 24;
@@ -91,6 +91,17 @@ public abstract class MapElement {
 		this.labels = Arrays.copyOf(labels, 4);
 	}
 
+	public int numLabels(){
+		int count = 0;
+		for (int i = 0; i < 4; i++) {
+			if (this.labels[i] != null) 
+				count++;
+			else 
+				break;
+		}
+		return count;
+	}
+
 	public ExtTypeAttributes getExtTypeAttributes() {
 		return extTypeAttributes;
 	}
diff --git a/src/uk/me/parabola/mkgmap/general/MapPoint.java b/src/uk/me/parabola/mkgmap/general/MapPoint.java
index 31e68c8..ed678a0 100644
--- a/src/uk/me/parabola/mkgmap/general/MapPoint.java
+++ b/src/uk/me/parabola/mkgmap/general/MapPoint.java
@@ -17,6 +17,7 @@ package uk.me.parabola.mkgmap.general;
 
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.util.Locatable;
 
 /**
  * A point on the map.  This will appear as a symbol on the map and it will
@@ -24,9 +25,8 @@ import uk.me.parabola.imgfmt.app.Coord;
  *
  * @author Steve Ratcliffe
  */
-public class MapPoint extends MapElement {
+public class MapPoint extends MapElement implements Locatable{
 	private Coord location;
-	private boolean isRoadNamePoi;
 
 	public MapPoint() {
 	}
@@ -62,14 +62,6 @@ public class MapPoint extends MapElement {
 		return isCityType(getType());
 	}
 
-	public void setRoadNamePOI(boolean isRoadNamePoi) {
-		this.isRoadNamePoi = isRoadNamePoi;
-	}
-
-	public boolean isRoadNamePOI() {
-		return this.isRoadNamePoi;
-	}
-	
 	public static boolean isCityType(int type)
 	{
 		return type >= 0x0100 && type <= 0x1100;
diff --git a/src/uk/me/parabola/mkgmap/general/MapRoad.java b/src/uk/me/parabola/mkgmap/general/MapRoad.java
index ed870bc..8df6315 100644
--- a/src/uk/me/parabola/mkgmap/general/MapRoad.java
+++ b/src/uk/me/parabola/mkgmap/general/MapRoad.java
@@ -40,19 +40,30 @@ public class MapRoad extends MapLine {
 
 	private final RoadDef roadDef;
 	private boolean segmentsFollowing;
+	private boolean skipHousenumberProcessing;
+	private boolean namedByHousenumberProcessing;
+	private final int roadId;
 	
-	public MapRoad(long id, MapLine line) {
+	public MapRoad(int roadId, long OSMid, MapLine line) {
 		super(line);
+		this.roadId = roadId;
 		setPoints(line.getPoints());
-		roadDef = new RoadDef(id, getName());
+		roadDef = new RoadDef(OSMid, getName());
 	}
 
 	private MapRoad(MapRoad r) {
 		super(r);
+		roadId = r.roadId;
 		roadDef = r.roadDef;
 		segmentsFollowing = r.segmentsFollowing;
 	}
 
+	/**
+	 * @return value that can be used to sort MapRoad instances
+	 */
+	public int getRoadId(){
+		return roadId;
+	}
 	public MapRoad copy() {
 		return new MapRoad(this);
 	}
@@ -101,32 +112,23 @@ public class MapRoad extends MapLine {
 		roadDef.setNoThroughRouting();
 	}
 
-	public void setStartsWithNode(boolean s) {
-		roadDef.setStartsWithNode(s);
-	}
-
-	public void setInternalNodes(boolean s) {
-		roadDef.setInternalNodes(s);
-	}
-
-	public void setNumNodes(int n) {
-		roadDef.setNumNodes(n);
-	}
-
 	public void setNumbers(List<Numbers> numbers) {
 		roadDef.setNumbersList(numbers);
 	}
+	public List<Numbers> getNumbers() {
+		return roadDef.getNumbersList();
+	}
 
 	public RoadDef getRoadDef() {
 		return roadDef;
 	}
 
-	public void setRoadCity(City c) {
-		roadDef.setCity(c);
+	public void addRoadCity(City c) {
+		roadDef.addCityIfNotPresent(c);
 	}
 
-	public void setRoadZip(Zip z) {
-		roadDef.setZip(z);
+	public void addRoadZip(Zip z) {
+		roadDef.addZipIfNotPresent(z);
 	}
 
 	public void setRoundabout(boolean r) {
@@ -149,4 +151,53 @@ public class MapRoad extends MapLine {
 		this.segmentsFollowing = segmentsFollowing;
 	}
 
+	public boolean isSkipHousenumberProcessing() {
+		return skipHousenumberProcessing;
+	}
+
+	public void setSkipHousenumberProcessing(boolean skipHousenumberProcessing) {
+		this.skipHousenumberProcessing = skipHousenumberProcessing;
+	}
+
+	public boolean isNamedByHousenumberProcessing() {
+		return namedByHousenumberProcessing;
+	}
+
+	public void setNamedByHousenumberProcessing(boolean namedByHousenumberProcessing) {
+		this.namedByHousenumberProcessing = namedByHousenumberProcessing;
+	}
+
+	public boolean skipAddToNOD() {
+		return roadDef.skipAddToNOD();
+	}
+
+	public void skipAddToNOD(boolean skip) {
+		roadDef.skipAddToNOD(skip);
+	}
+
+	public boolean addLabel(String label){
+		if (label == null)
+			return false;
+		for (int i = 0; i < labels.length; i++){
+			if (labels[i] == null){
+				labels[i] = label;
+				return true;
+			}
+			if (labels[i].equals(label))
+				return false;
+		}
+		return false;
+	}
+	
+	public String toString(){
+		if ((getName() == null || getName().isEmpty()) && getStreet() != null)
+			return "id="+this.getRoadDef().getId() + ", (" + this.getStreet() + ")";
+		else 
+			return "id="+this.getRoadDef().getId() + ", " + this.getName();
+	}
+
+	public void resetImgData() {
+		roadDef.resetImgData();
+		
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/general/ZipCodeInfo.java b/src/uk/me/parabola/mkgmap/general/ZipCodeInfo.java
new file mode 100644
index 0000000..a346f98
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/general/ZipCodeInfo.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+package uk.me.parabola.mkgmap.general;
+
+import uk.me.parabola.imgfmt.app.lbl.Zip;
+
+public class ZipCodeInfo implements Comparable<ZipCodeInfo> {
+	private static final String UNKNOWN = "?";
+	private final String zipCode;
+	private Zip imgZip;
+
+	public ZipCodeInfo (String zipCode){
+		this.zipCode = (zipCode != null) ? zipCode: UNKNOWN;
+	}
+
+	public String getZipCode() {
+		if (zipCode == UNKNOWN)
+			return null;
+		return zipCode;
+	}
+
+	public void setImgZip(Zip zip){
+		imgZip = zip;
+	}
+	
+	
+	public Zip getImgZip() {
+		return imgZip;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((zipCode == null) ? 0 : zipCode.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (!(obj instanceof ZipCodeInfo))
+			return false;
+		ZipCodeInfo other = (ZipCodeInfo) obj;
+		if (zipCode == null) {
+			if (other.zipCode != null)
+				return false;
+		} else if (!zipCode.equals(other.zipCode))
+			return false;
+		return true;
+	}
+
+	@Override
+	public int compareTo(ZipCodeInfo o) {
+		if (this == o)
+			return 0;
+		return zipCode.compareTo(o.zipCode);
+	}
+
+	@Override
+	public String toString() {
+		return zipCode; 
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/main/MapMaker.java b/src/uk/me/parabola/mkgmap/main/MapMaker.java
index 5b58556..f3309f4 100644
--- a/src/uk/me/parabola/mkgmap/main/MapMaker.java
+++ b/src/uk/me/parabola/mkgmap/main/MapMaker.java
@@ -18,30 +18,19 @@ package uk.me.parabola.mkgmap.main;
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 
 import uk.me.parabola.imgfmt.FileExistsException;
 import uk.me.parabola.imgfmt.FileNotWritableException;
 import uk.me.parabola.imgfmt.FileSystemParam;
 import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.MapFailedException;
-import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.map.Map;
 import uk.me.parabola.imgfmt.app.srt.Sort;
-import uk.me.parabola.imgfmt.app.srt.SortKey;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.CommandArgs;
 import uk.me.parabola.mkgmap.build.MapBuilder;
 import uk.me.parabola.mkgmap.combiners.OverviewBuilder;
 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
-import uk.me.parabola.mkgmap.general.MapLine;
-import uk.me.parabola.mkgmap.general.MapPoint;
-import uk.me.parabola.mkgmap.general.MapRoad;
 import uk.me.parabola.mkgmap.reader.plugin.MapReader;
 
 /**
@@ -62,8 +51,6 @@ public class MapMaker implements MapProcessor {
 		try {
 			LoadableMapDataSource src = loadFromFile(args, filename);
 			sort = args.getSort();
-			log.info("Making Road Name POIs for", filename);
-			makeRoadNamePOIS(args, src);
 			if (createOverviewFiles){
 				if (src.overviewMapLevels() != null){
 					makeMap(args, src, OverviewBuilder.OVERVIEW_PREFIX);
@@ -168,165 +155,4 @@ public class MapMaker implements MapProcessor {
 		log.info("Finished loading", name);
 		return src;
 	}
-
-	void makeRoadNamePOIS(CommandArgs args, LoadableMapDataSource src) {
-		String rnp = args.get("road-name-pois", null);
-		// are road name POIS wanted?
-		if(rnp != null) {
-			rnp = rnp.toUpperCase();
-			int rnpt = 0x640a; // Garmin type 'Locale'
-			if(rnp.length() > 0) {
-				// override type code
-				rnpt = Integer.decode(rnp);
-			}
-			// collect lists of roads that have the same name
-			java.util.Map<String, List<MapRoad>> namedRoads = new HashMap<String, List<MapRoad>>();
-			for(MapLine l : src.getLines()) {
-				if(l.isRoad()) {
-					MapRoad r = (MapRoad)l;
-					String rn = r.getName();
-					if(rn != null) {
-						List<MapRoad> rl = namedRoads.get(rn);
-						if(rl == null) {
-							rl = new ArrayList<MapRoad>();
-							namedRoads.put(rn, rl);
-						}
-						rl.add(r);
-					}
-				}
-			}
-
-			// generate a POI for each named road
-
-			// sort by name and coordinate of first point so that
-			// the order is always the same for the same input
-			List<SortKey<MapRoad>> rnpRoads = new ArrayList<SortKey<MapRoad>>();
-			for(List<MapRoad> lr : findConnectedRoadsWithSameName(namedRoads)) {
-				// connected roads are not ordered so just use first in list
-				MapRoad r = lr.get(0);
-				String key = r.getName();
-				List<Coord> points = r.getPoints();
-				if(!points.isEmpty())
-					key += "_" + points.get(0);
-				rnpRoads.add(sort.createSortKey(r, key));
-			}
-			Collections.sort(rnpRoads);
-			for(SortKey<MapRoad> sr : rnpRoads)
-				src.getPoints().add(makeRoadNamePOI(sr.getObject(), rnpt));
-		}
-	}
-
-	private boolean roadsAreJoined(MapLine r1, MapLine r2) {
-		if (r1.getBounds().intersects(r2.getBounds()) == false)  {
-			return false;
-		}
-
-		if(r1 != r2) {
-			for(Coord c1 : r1.getPoints()) {
-				for(Coord c2 : r2.getPoints()) {
-					if(c1 == c2 || c1.highPrecEquals(c2))
-						return true;
-				}
-			}
-		}
-		return false;
-	}
-
-	// hairy function to build a set of lists - each list contains
-	// the roads that have the same name and are connected
-
-	private Set<List<MapRoad>> findConnectedRoadsWithSameName(java.util.Map<String, List<MapRoad>> namedRoads) {
-		// roadGroups is a set to avoid duplicate groups
-		Set<List<MapRoad>> roadGroups = new HashSet<List<MapRoad>>();
-
-		// loop over the lists of roads that have the same name
-		for(List<MapRoad> allRoadsWithSameName : namedRoads.values()) {
-			// for each road that has the same name, keep track of its group
-			java.util.Map<MapRoad,List<MapRoad>> roadGroupMap = new HashMap<MapRoad,List<MapRoad>>();
-
-			// loop over all of the roads with the same name
-			for(int i = 0; i < allRoadsWithSameName.size(); ++i) {
-				boolean roadWasJoined = false;
-				for(int j = 0; j < allRoadsWithSameName.size(); ++j) {
-					if(i != j) {
-						// see if these two roads are joined
-						MapRoad ri = allRoadsWithSameName.get(i);
-						MapRoad rj = allRoadsWithSameName.get(j);
-						if(roadsAreJoined(ri, rj)) {
-							// yes, they are joined so put both in a group
-							// and associate the group with each road
-							roadWasJoined = true;
-							List<MapRoad> groupi = roadGroupMap.get(ri);
-							List<MapRoad> groupj = roadGroupMap.get(rj);
-							if(groupi == null) {
-								// ri is not in a group yet
-								if(groupj == null) {
-									// neither is rj so make a new group
-									groupi = new ArrayList<MapRoad>();
-									groupi.add(ri);
-									groupi.add(rj);
-									roadGroupMap.put(ri, groupi);
-									roadGroupMap.put(rj, groupi);
-								}
-								else {
-									// add ri to groupj
-									groupj.add(ri);
-									roadGroupMap.put(ri, groupj);
-								}
-							}
-							else if(groupj == null) {
-								// add rj to groupi
-								groupi.add(rj);
-								roadGroupMap.put(rj, groupi);
-							}
-							else if(groupi != groupj) {
-								// ri and rj are in separate groups so put
-								// all the roads in groupj into groupi
-								for(MapRoad r : groupj)
-									roadGroupMap.put(r, groupi);
-								groupi.addAll(groupj);
-							}
-						}
-					}
-				}
-				if(!roadWasJoined) {
-					// make a group with just one entry
-					MapRoad ri = allRoadsWithSameName.get(i);
-					List<MapRoad>group = new ArrayList<MapRoad>();
-					group.add(ri);
-					roadGroupMap.put(ri, group);
-				}
-			}
-
-			// now add the new group(s) to the final result
-			for(List<MapRoad> r : roadGroupMap.values())
-				roadGroups.add(r);
-		}
-		return roadGroups;
-	}
-
-	private MapPoint makeRoadNamePOI(MapRoad road, int type) {
-		List<Coord> points = road.getPoints();
-		int numPoints = points.size();
-		Coord coord;
-		// XXX Why not always use an existing point close to
-		// numpoints/2 ?
-		if ((numPoints & 1) == 0) {
-			int i2 = numPoints / 2;
-			int i1 = i2 - 1;
-			coord = points.get(i1).makeBetweenPoint(points.get(i2), 0.5);
-			
-		} else {
-			coord = points.get(numPoints / 2);
-		}
-
-		String name = road.getName();
-		MapPoint rnp = new MapPoint();
-
-		rnp.setName(name);
-		rnp.setRoadNamePOI(true);
-		rnp.setType(type);
-		rnp.setLocation(coord);
-		return rnp;
-	}
 }
diff --git a/src/uk/me/parabola/mkgmap/main/StyleTester.java b/src/uk/me/parabola/mkgmap/main/StyleTester.java
index cf49a2f..5024c47 100644
--- a/src/uk/me/parabola/mkgmap/main/StyleTester.java
+++ b/src/uk/me/parabola/mkgmap/main/StyleTester.java
@@ -676,6 +676,13 @@ public class StyleTester implements OsmConverter {
 			}
 
 			@Override
+			public Rule getFinalizeRule() {
+				if (rules.isEmpty())
+					return null;
+				return rules.get(0).getFinalizeRule();
+			}
+
+			@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 8151c1a..187cee6 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
@@ -166,6 +166,10 @@ public class ActionRule implements Rule {
 		this.finalizeRule = finalizeRule;
 	}
 
+	@Override
+	public Rule getFinalizeRule() {
+		return finalizeRule;
+	}
 
 	public Op getOp(){
 		return expression;
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
index 7df93f7..eea666c 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
@@ -95,6 +95,11 @@ public class ExpressionRule implements Rule {
 		this.finalizeRule = finalizeRule;
 	}
 	
+	@Override
+	public Rule getFinalizeRule() {
+		return finalizeRule;
+	}
+
 	public Op getOp(){
 		return expression;
 	}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java b/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
index 9c2581a..5d7a254 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
@@ -72,6 +72,7 @@ public class RoadMerger {
 			add("mkgmap:synthesised");
 			add("mkgmap:highest-resolution-only");
 			add("mkgmap:flare-check");
+			add("mkgmap:numbers");
 		}
 	};
 
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
index 77a1f1f..b7048b5 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
@@ -33,6 +33,7 @@ import uk.me.parabola.mkgmap.osmstyle.eval.LinkedOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.Op;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.Rule;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.TypeResult;
 import uk.me.parabola.mkgmap.reader.osm.WatchableTypeResult;
 
@@ -52,6 +53,8 @@ public class RuleSet implements Rule, Iterable<Rule> {
 	int cacheId;
 	boolean compiled = false;
 
+	private final static short executeFinalizeRulesTagKey = TagDict.getInstance().xlate("mkgmap:execute_finalize_rules");
+
 	private RuleIndex index = new RuleIndex();
 	private final Set<String> usedTags = new HashSet<String>();
 	
@@ -87,12 +90,19 @@ public class RuleSet implements Rule, Iterable<Rule> {
 			if (rules != null && !rules.isEmpty() )
 				candidates.or(rules);
 		}
+		Rule lastRule = null;
 		for (int i = candidates.nextSetBit(0); i >= 0; i = candidates.nextSetBit(i + 1)) {			
 			a.reset();
-			cacheId = rules[i].resolveType(cacheId, el, a);
+			lastRule = rules[i];
+			cacheId = lastRule.resolveType(cacheId, el, a);
 			if (a.isResolved())
 				return cacheId;
 		}
+		if (lastRule != null && lastRule.getFinalizeRule() != null){
+			if ("true".equals(el.getTag(executeFinalizeRulesTagKey))){
+				cacheId = lastRule.getFinalizeRule().resolveType(cacheId, el, a);
+			}
+		}
 		return cacheId;
 	}
 
@@ -264,6 +274,11 @@ public class RuleSet implements Rule, Iterable<Rule> {
 	}
 
 	@Override
+	public Rule getFinalizeRule() {
+		return finalizeRule;
+	}
+
+	@Override
 	public void printStats(String header) {
 		if (rules == null)
 		  return;
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index 95300a3..81a5673 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -120,6 +120,7 @@ public class StyledConverter implements OsmConverter {
 	private HashSet<Long> deletedRoads = new HashSet<>();
 
 	private int nextNodeId = 1;
+	private int nextRoadId = 1;
 	
 	private HousenumberGenerator housenumberGenerator;
 	
@@ -142,6 +143,7 @@ public class StyledConverter implements OsmConverter {
 	private int reportDeadEnds; 
 	private final boolean linkPOIsToWays;
 	private final boolean mergeRoads;
+	private final boolean routable;
 	
 
 	private LineAdder lineAdder = new LineAdder() {
@@ -204,7 +206,7 @@ public class StyledConverter implements OsmConverter {
 		
 		// undocumented option - usually used for debugging only
 		mergeRoads = props.getProperty("no-mergeroads", false) == false;
-
+		routable = props.containsKey("route");
 		
 	}
 
@@ -229,7 +231,8 @@ public class StyledConverter implements OsmConverter {
 					el = way.copy();
 			}
 			postConvertRules(el, type);
-			housenumberGenerator.addWay((Way)el);
+			if (type.isRoad() == false)
+				housenumberGenerator.addWay((Way)el);
 			addConvertedWay((Way) el, type);
 		}
 
@@ -279,7 +282,6 @@ public class StyledConverter implements OsmConverter {
 			else
 				rules = wayRules;
 		}
-
 		Way cycleWay = null;
 		String cycleWayTag = way.getTag(makeCycleWayTagKey);
 		if ("yes".equals(cycleWayTag)){
@@ -614,57 +616,61 @@ public class StyledConverter implements OsmConverter {
 			if (cw.isValid())
 				addRoad(cw);
 		}
-		housenumberGenerator.generate(lineAdder);
+		housenumberGenerator.generate(lineAdder, nextNodeId);
+		housenumberGenerator = null;
 		
-		createRouteRestrictionsFromPOI();
+		if (routable)
+			createRouteRestrictionsFromPOI();
 		poiRestrictions = null;
-		
-		for (RestrictionRelation rr : restrictions) {
-			rr.addRestriction(collector, nodeIdMap);
+		if (routable){
+			for (RestrictionRelation rr : restrictions) {
+				rr.addRestriction(collector, nodeIdMap);
+			}
 		}
 		roads = null;
-
-		for(Relation relation : throughRouteRelations) {
-			Node node = null;
-			Way w1 = null;
-			Way w2 = null;
-			for(Map.Entry<String,Element> member : relation.getElements()) {
-				if(member.getValue() instanceof Node) {
-					if(node == null)
-						node = (Node)member.getValue();
-					else
-						log.warn("Through route relation", relation.toBrowseURL(), "has more than 1 node");
-				}
-				else if(member.getValue() instanceof Way) {
-					Way w = (Way)member.getValue();
-					if(w1 == null)
-						w1 = w;
-					else if(w2 == null)
-						w2 = w;
-					else
-						log.warn("Through route relation", relation.toBrowseURL(), "has more than 2 ways");
+		if (routable){
+			for(Relation relation : throughRouteRelations) {
+				Node node = null;
+				Way w1 = null;
+				Way w2 = null;
+				for(Map.Entry<String,Element> member : relation.getElements()) {
+					if(member.getValue() instanceof Node) {
+						if(node == null)
+							node = (Node)member.getValue();
+						else
+							log.warn("Through route relation", relation.toBrowseURL(), "has more than 1 node");
+					}
+					else if(member.getValue() instanceof Way) {
+						Way w = (Way)member.getValue();
+						if(w1 == null)
+							w1 = w;
+						else if(w2 == null)
+							w2 = w;
+						else
+							log.warn("Through route relation", relation.toBrowseURL(), "has more than 2 ways");
+					}
 				}
-			}
 
-			CoordNode coordNode = null;
-			if(node == null)
-				log.warn("Through route relation", relation.toBrowseURL(), "is missing the junction node");
-			else {
-				Coord junctionPoint = node.getLocation();
-				if(bbox != null && !bbox.contains(junctionPoint)) {
-					// junction is outside of the tile - ignore it
-					continue;
+				CoordNode coordNode = null;
+				if(node == null)
+					log.warn("Through route relation", relation.toBrowseURL(), "is missing the junction node");
+				else {
+					Coord junctionPoint = node.getLocation();
+					if(bbox != null && !bbox.contains(junctionPoint)) {
+						// junction is outside of the tile - ignore it
+						continue;
+					}
+					coordNode = nodeIdMap.get(junctionPoint);
+					if(coordNode == null)
+						log.warn("Through route relation", relation.toBrowseURL(), "junction node at", junctionPoint.toOSMURL(), "is not a routing node");
 				}
-				coordNode = nodeIdMap.get(junctionPoint);
-				if(coordNode == null)
-					log.warn("Through route relation", relation.toBrowseURL(), "junction node at", junctionPoint.toOSMURL(), "is not a routing node");
-			}
 
-			if(w1 == null || w2 == null)
-				log.warn("Through route relation", relation.toBrowseURL(), "should reference 2 ways that meet at the junction node");
+				if(w1 == null || w2 == null)
+					log.warn("Through route relation", relation.toBrowseURL(), "should reference 2 ways that meet at the junction node");
 
-			if(coordNode != null && w1 != null && w2 != null)
-				collector.addThroughRoute(coordNode.getId(), w1.getId(), w2.getId());
+				if(coordNode != null && w1 != null && w2 != null)
+					collector.addThroughRoute(coordNode.getId(), w1.getId(), w2.getId());
+			}
 		}
 		// return memory to GC
 		nodeIdMap = null;
@@ -1512,26 +1518,27 @@ public class StyledConverter implements OsmConverter {
 			if((i + 1) < points.size()) {
 				Coord nextP = points.get(i + 1);
 				double d = p.distance(nextP);
-				// get arc size as a proportion of the max allowed - a
-				// value greater than 1.0 indicate that the bbox is
-				// too large in at least one dimension
-				double arcProp = LineSizeSplitterFilter.testDims(nextP.getLatitude() -
-																 p.getLatitude(),
-																 nextP.getLongitude() -
-																 p.getLongitude());
-				if(arcProp >= 1.0 || d > MAX_ARC_LENGTH) {
-					nextP = p.makeBetweenPoint(nextP, 0.95 * Math.min(1 / arcProp, MAX_ARC_LENGTH / d));
-					nextP.incHighwayCount();
-					points.add(i + 1, nextP);
-					double newD = p.distance(nextP);
-					if (log.isInfoEnabled())
-						log.info("Way", debugWayName, "contains a segment that is", (int)d + "m long but I am adding a new point to reduce its length to", (int)newD + "m");
-					d = newD;
+				for (;;){
+					int dlat = Math.abs(nextP.getLatitude() - p.getLatitude());
+					int dlon = Math.abs(nextP.getLongitude() - p.getLongitude());
+					if (d > MAX_ARC_LENGTH || Math.max(dlat,  dlon) >= LineSizeSplitterFilter.MAX_SIZE){
+						double frac = Math.min(0.5,	 0.95 * (MAX_ARC_LENGTH / d));
+						nextP = p.makeBetweenPoint(nextP, frac);
+						nextP.incHighwayCount();
+						points.add(i + 1, nextP);
+						double newD = p.distance(nextP);
+						if (log.isInfoEnabled())
+							log.info("Way", debugWayName, "contains a segment that is", (int)d + "m long but I am adding a new point to reduce its length to", (int)newD + "m");
+						d = newD;
+					} else 
+						break;
 				}
-
+				
 				wayBBox.addPoint(nextP);
 
 				if((arcLength + d) > MAX_ARC_LENGTH) {
+					if (i <= 0)
+						log.error("internal error: long arc segment was not split", debugWayName);
 					assert i > 0 : "long arc segment was not split";
 					assert trailingWay == null : "trailingWay not null #1";
 					trailingWay = splitWayAt(way, i);
@@ -1541,6 +1548,8 @@ public class StyledConverter implements OsmConverter {
 						log.info("Splitting way", debugWayName, "at", points.get(i).toOSMURL(), "to limit arc length to", (long)arcLength + "m");
 				}
 				else if(wayBBox.tooBig()) {
+					if (i <= 0)
+						log.error("internal error: arc segment with big bbox not split", debugWayName);
 					assert i > 0 : "arc segment with big bbox not split";
 					assert trailingWay == null : "trailingWay not null #2";
 					trailingWay = splitWayAt(way, i);
@@ -1608,8 +1617,10 @@ public class StyledConverter implements OsmConverter {
 		MapLine line = new MapLine();
 		elementSetup(line, cw.getGType(), way);
 		line.setPoints(points);
-		MapRoad road = new MapRoad(way.getId(), line);
-
+		MapRoad road = new MapRoad(nextRoadId++, way.getId(), line);
+		if (routable == false)
+			road.skipAddToNOD(true);
+		
 		boolean doFlareCheck = true;
 		
 		if (cw.isRoundabout()){
@@ -1670,14 +1681,11 @@ public class StyledConverter implements OsmConverter {
 				rr.updateViaWay(way, nodeIndices);
 			}
 		}
-		road.setNumNodes(numNodes);
+
 		if(numNodes > 0) {
 			// replace Coords that are nodes with CoordNodes
-			boolean hasInternalNodes = false;
 			for(int i = 0; i < numNodes; ++i) {
 				int n = nodeIndices.get(i);
-				if(n > 0 && n < points.size() - 1)
-					hasInternalNodes = true;
 				Coord coord = points.get(n);
 				CoordNode thisCoordNode = nodeIdMap.get(coord);
 				assert thisCoordNode != null : "Way " + debugWayName + " node " + i + " (point index " + n + ") at " + coord.toOSMURL() + " yields a null coord node";
@@ -1687,9 +1695,6 @@ public class StyledConverter implements OsmConverter {
 				}
 				points.set(n, thisCoordNode);
 			}
-
-			road.setStartsWithNode(nodeIndices.get(0) == 0);
-			road.setInternalNodes(hasInternalNodes);
 		}
 
 		if (roadLog.isInfoEnabled()) {
@@ -2044,6 +2049,7 @@ public class StyledConverter implements OsmConverter {
 		}
 	}
 
+	@Override
 	public Boolean getDriveOnLeft(){
 		assert roads == null : "getDriveOnLeft() should be called after end()";
 		return driveOnLeft;
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java
new file mode 100644
index 0000000..4f4f26b
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java
@@ -0,0 +1,1960 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * 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.housenumber;
+
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.net.NumberStyle;
+import uk.me.parabola.imgfmt.app.net.Numbers;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.filters.LineSplitterFilter;
+import uk.me.parabola.mkgmap.general.CityInfo;
+import uk.me.parabola.mkgmap.general.MapRoad;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
+import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByPosComparator;
+
+/**
+ * Helper class to allow easy corrections like splitting.
+ * 
+ * If we want to split an interval because it overlaps
+ * with another one, we have different options.
+ * 1) if the interval covers multiple points of the road, we may change a point to a number node
+ * 2) or if the road segment is long enough we may add a point to split it,
+ * long enough means that the we can find a point that is so close to the original
+ * line that it is not too distorting,   
+ * 3) or we may duplicate the node at one end (or both) and move some of the numbers to that new
+ * zero-length-interval.
+ * 
+ * When we find no more overlaps, we can start to reduce the distance
+ * of the calculated position (the result of the address search in 
+ * Garmin products) and the known best position on the road.
+ * This is a bit tricky: Garmin software places  
+ * a)  a single house in the middle of the 
+ * segment covered by the interval
+ * b) two or more houses are placed so that the first
+ * is at the very beginning, the last is at the very end,
+ * the rest is between them with equal distances.
+ * The problem: We can have houses on both sides of the segment,
+ * so the optimal length for the left side may not be the
+ * best for the right side. 
+ * We try to find a good compromise between good search result
+ * and the number of additional intervals. 
+ *  
+ * @author GerdP
+ *
+ */
+public class ExtNumbers {
+	private static final Logger log = Logger.getLogger(ExtNumbers.class);
+
+	private final HousenumberRoad housenumberRoad;
+	private static final int MAX_LOCATE_ERROR = 40; 
+	
+	private static final List<HousenumberMatch> NO_HOUSES = Collections.emptyList();
+	
+	public ExtNumbers prev,next;
+	class RoadSide {
+		List<HousenumberMatch> houses = Collections.emptyList();
+		boolean multipleZipCodes;
+		boolean multipleCities;
+		boolean notInOrder;
+	}
+	
+	private RoadSide leftSide,rightSide;
+	private Numbers numbers = null;
+	private int startInRoad, endInRoad;
+	private int nodeIndex;
+	
+	private boolean needsSplit;
+	private HousenumberMatch worstHouse;
+	
+	// indicates a number that is found in the interval, but should not   
+	private int badNum;
+	
+	private boolean hasGaps; // true if interval covers more numbers than known
+
+	// constants representing reasons for splitting
+	public static final int SR_FIX_ERROR = 0;
+	public static final int SR_OPT_LEN = 1;
+	public static final int SR_SPLIT_ROAD_END = 2;
+	
+	public ExtNumbers(HousenumberRoad housenumberRoad) {
+		super();
+		this.housenumberRoad = housenumberRoad;
+		reset();
+	}
+
+	private void setNeedsSplit(boolean b) {
+		needsSplit = true;
+	}
+
+	public boolean needsSplit() {
+		return needsSplit;
+	}
+
+	private boolean notInOrder(boolean left){
+		RoadSide rs = (left) ? leftSide : rightSide;
+		return (rs != null) ? rs.notInOrder : false; 
+	}
+
+	private List<HousenumberMatch> getHouses(boolean left){
+		RoadSide rs = (left) ? leftSide : rightSide;
+		return (rs != null) ? rs.houses : NO_HOUSES;  
+	}
+
+	private MapRoad getRoad(){
+		return housenumberRoad.getRoad();
+	}
+	
+	private void reset() {
+		numbers = null;
+		needsSplit = false;
+		hasGaps = false;
+	}
+	
+	
+	public void setNodeIndex(int nodeIndex) {
+		this.nodeIndex = nodeIndex; 
+		if (numbers != null)
+			numbers.setIndex(nodeIndex);
+	}
+
+
+	
+	public Numbers getNumbers() {
+		if (numbers == null){
+			numbers = new Numbers();
+			numbers.setIndex(nodeIndex);
+			fillNumbers(Numbers.LEFT);
+			fillNumbers(Numbers.RIGHT);
+			if (!numbers.isEmpty()){
+				verify(getHouses(Numbers.LEFT)); // TODO : remove 
+				verify(getHouses(Numbers.RIGHT)); // TODO : remove
+			}
+		}
+		return numbers;
+	}
+
+	/**
+	 * Store given house numbers and meta info
+	 * @param housenumbers a list of house numbers, sorted by appearance on the road
+	 * @param startSegment index of road point where this segment starts
+	 * @param endSegment index of road point where this segment ends
+	 * @param left {@code true} the left side of the street; {@code false} the right side of the street
+	 * @return the number of elements which were used (from the beginning)
+	 */
+	public int setNumbers(List<HousenumberMatch> housenumbers, int startSegment, int endSegment, boolean left) {
+		int assignedNumbers = 0;
+		if (housenumbers.isEmpty() == false) {
+			RoadSide rs = new RoadSide(); 
+			if (left)
+				leftSide = rs;
+			else
+				rightSide = rs;
+			// get the sublist of house numbers
+			int maxN = -1;
+			int numHouses = housenumbers.size();
+			for (int i = 0; i< numHouses; i++) {
+				HousenumberMatch house = housenumbers.get(i);
+				if (house.isIgnored())
+					continue;
+				if (house.getSegment() >= endSegment) {
+					break;
+				} 
+				maxN = i;
+			}
+			
+			if (maxN >= 0) {
+				assignedNumbers = maxN + 1;
+				rs.houses = new ArrayList<>(housenumbers.subList(0, assignedNumbers));
+				startInRoad = startSegment;
+				endInRoad = endSegment;
+				assert startSegment < endSegment;
+				if (getRoad().getPoints().get(startInRoad).isNumberNode() == false || getRoad().getPoints().get(endInRoad).isNumberNode() == false){
+					log.error("internal error: start or end is not a number node", this);
+				}
+			}
+		}
+		return assignedNumbers;
+	}
+	
+	/**
+	 * Apply the given house numbers to the numbers object.
+	 * @param left {@code true} the left side of the street; {@code false} the right side of the street
+	 */
+	private void fillNumbers(boolean left) {
+		NumberStyle style = NumberStyle.NONE;
+		List<HousenumberMatch> houses = getHouses(left);
+		if (houses.isEmpty() == false) {
+			Set<CityInfo> cityInfos = new HashSet<>();
+			Set<ZipCodeInfo> zipCodes = new HashSet<>();
+			// get the sublist of house numbers
+			boolean even = false;
+			boolean odd = false;
+			boolean inOrder = true;
+			boolean inc = false;
+			boolean dec = false;
+			HousenumberMatch highest, lowest;
+			lowest = highest = houses.get(0);
+			Int2IntOpenHashMap distinctNumbers = new Int2IntOpenHashMap();
+			int numHouses = houses.size();
+			HousenumberMatch pred = null;
+			for (int i = 0; i< numHouses; i++) {
+				HousenumberMatch house = houses.get(i);
+				if (house.getCityInfo() != null && house.getCityInfo().isEmpty() == false)
+					cityInfos.add(house.getCityInfo());
+				if (house.getZipCode() != null && house.getZipCode().getZipCode() != null)
+					zipCodes.add(house.getZipCode());
+				int num = house.getHousenumber();
+				if (!hasGaps)
+					distinctNumbers.put(num, 1);
+				if (num > highest.getHousenumber())
+					highest = house;
+				if (num < lowest.getHousenumber())
+					lowest = house;
+				if (num % 2 == 0) {
+					even = true;
+				} else {
+					odd = true;
+				}
+				
+				if (pred != null){
+					int diff = num - pred.getHousenumber();
+					if(diff > 0)
+						inc = true;
+					else if (diff < 0)
+						dec = true;
+				}
+				pred = house;
+			}
+			
+			if (even && odd) {
+				style = NumberStyle.BOTH;
+			} else if (even) {
+				style = NumberStyle.EVEN;
+			} else {
+				style = NumberStyle.ODD;
+			}
+			int highestNum = highest.getHousenumber();
+			int lowestNum = lowest.getHousenumber();
+			int start = houses.get(0).getHousenumber();
+			int end = houses.get(numHouses-1).getHousenumber();
+			boolean increasing = false; // from low to high
+			if (dec & inc)
+				inOrder = false;
+			if (start == end && highestNum - lowestNum != 0){
+				if (prev != null){
+					int lastEnd = prev.getNumbers().getEnd(left );
+					if (lastEnd <= lowestNum)
+						increasing = true;
+				} else if (next != null){
+					int nextStart = next.getNumbers().getStart(left);
+					if (highestNum < nextStart)
+						increasing = true;
+				} else {
+					increasing = true;
+				}
+			}
+			else if (start != highestNum && start != lowestNum
+					|| end != highestNum && end != lowestNum) {
+				inOrder = false;
+				if (start <= end)
+					increasing = true;
+			} else if (start < end){
+				increasing = true;
+			}
+			if (increasing){
+				start = lowestNum;
+				end = highestNum;
+			} else {
+				start = highestNum;
+				end = lowestNum;
+			}
+			if (!hasGaps){
+				int step = (style == NumberStyle.BOTH) ? 1 : 2;
+				for (int n = lowestNum+step; n < highestNum; n += step){
+					if (distinctNumbers.containsKey(n))
+						continue;
+					hasGaps = true;
+					break;
+				}
+			}
+			RoadSide rs = (left) ? leftSide : rightSide;
+			numbers.setNumbers(left, style, start, end);
+			rs.multipleCities = (cityInfos.size() > 1);
+			rs.multipleZipCodes = (zipCodes.size() > 1);
+			if (cityInfos.size() == 1){
+				CityInfo ci = cityInfos.iterator().next();
+				if (ci.isEmpty() == false){
+					if (ci.equals(housenumberRoad.getRoadCityInfo()) == false)
+						numbers.setCityInfo(left, ci);
+				}
+			}
+			
+			if (zipCodes.size() == 1){
+				ZipCodeInfo zipCodeInfo = zipCodes.iterator().next();
+				if (zipCodeInfo.getZipCode() != null){
+					if (zipCodeInfo.equals(housenumberRoad.getRoadZipCode()) == false){
+						// we found a zip code and the road doesn't yet have one, use it for the whole road
+						if (getRoad().getZip() == null){
+							housenumberRoad.setZipCodeInfo(zipCodeInfo);
+						} else
+							numbers.setZipCode(left, zipCodeInfo);
+					}
+				}
+			}
+			rs.notInOrder = !inOrder;
+		}
+	}
+
+	/**
+	 * Return the intervals in the format used for the writer routines
+	 * @return
+	 */
+	public List<Numbers> getNumberList() {
+		// do we have numbers?
+		boolean foundNumbers = false;
+		for (ExtNumbers curr = this; curr != null; curr = curr.next){
+			if (curr.hasNumbers()){
+				foundNumbers = true;
+				break;
+			}
+		}
+		if (!foundNumbers)
+			return null;
+		
+		List<Numbers> list = new ArrayList<>();
+		for (ExtNumbers curr = this; curr != null; curr = curr.next){
+			if (curr.hasNumbers() == false)
+				continue;
+			list.add(curr.getNumbers());
+			if (log.isInfoEnabled()) {
+				if (curr.prev == null){
+					MapRoad road = curr.getRoad();
+					if (road.getStreet() == null && road.getName() == null)
+						log.info("final numbers for", road, curr.housenumberRoad.getName(), "in", road.getCity());
+					else 
+						log.info("final numbers for", road, "in", road.getCity());
+				}
+				Numbers cn = curr.getNumbers();
+				log.info("Left: ",cn.getLeftNumberStyle(),cn.getIndex(),"Start:",cn.getLeftStart(),"End:",cn.getLeftEnd(), "numbers "+curr.getHouses(Numbers.LEFT));
+				log.info("Right:",cn.getRightNumberStyle(),cn.getIndex(),"Start:",cn.getRightStart(),"End:",cn.getRightEnd(), "numbers "+curr.getHouses(Numbers.RIGHT));
+			}
+		}
+		return list;
+	}
+	
+	public ExtNumbers checkSingleChainSegments(String streetName, boolean removeGaps) {
+		ExtNumbers curr = this;
+		ExtNumbers head = this;
+		if (housenumberRoad.isRandom() || removeGaps){
+			for (curr = head; curr != null; curr = curr.next){
+				while (curr.hasGaps && (removeGaps || curr.notInOrder(true) || curr.notInOrder(false))){
+					curr.worstHouse = null;
+					curr.badNum = -1;
+					ExtNumbers test = curr.tryChange(SR_FIX_ERROR);
+					if (test != curr){
+						if (curr.prev == null)
+							head = test;
+						curr = test;
+					}
+					else {
+						log.warn("can't split numbers interaval for road", curr.getNumbers(), curr);
+						break;
+					}
+				}
+
+			}
+		}
+		for (curr = head; curr != null; curr = curr.next){
+			while (curr.isPlausible() == false){
+				// this happens in the following cases:
+				// 1. correct OSM data, multiple houses build a block. Standing on the road
+				// you probably see a small service road which leads to the houses.
+				// It is okay to use each of them. 
+				// 2. correct OSM data, one or more house should be connected to a 
+				// different road with the same name, we want to ignore them 
+				// 3. wrong OSM data, one or more numbers are wrong, we want to ignore them
+				// 4. other cases, e.g. numbers 1,3,5 followed by 10,14,12. This should be fixed
+				// by splitting the segment first, as the OSM data might be correct.
+				if (log.isInfoEnabled())
+					log.info("detected unplausible interval in",streetName, curr.getNumbers(),"in road", getRoad());
+				if (log.isDebugEnabled()){
+					if (curr.notInOrder(Numbers.LEFT))
+						log.debug("left numbers not in order:", getRoad(), curr.getHouses(Numbers.LEFT));
+					if (curr.notInOrder(Numbers.RIGHT))
+						log.debug("right numbers not in order:", getRoad(), curr.getHouses(Numbers.RIGHT));
+				}
+				curr.setNeedsSplit(true);
+				curr.findGoodSplitPos();
+				ExtNumbers test = curr.tryChange(SR_FIX_ERROR);
+				if (test != curr){
+					housenumberRoad.setChanged(true);
+					if (curr.prev == null)
+						head = test;
+					curr = test;
+				}
+				else {
+					log.warn("can't fix unplausible numbers interaval for road",curr.getNumbers(),curr);
+					break;
+				}
+			}
+		}
+		return head;
+	}
+
+	private void verify(List<HousenumberMatch> houses) {
+		for (HousenumberMatch house : houses){
+			if (house.isIgnored())
+				continue;
+			if (house.getSegment() < startInRoad || house.getSegment() >= endInRoad){
+				log.error("internal error, house has wrong segment, road",getRoad(),"house",house,house.getElement().toBrowseURL());
+			}
+			if (Double.isNaN(house.getDistance()) || house.getDistance() > HousenumberGenerator.MAX_DISTANCE_TO_ROAD + 10){
+				if (house.getGroup() == null)
+					log.error("internal error, distance to road too large, road",getRoad(),"house",house,house.getElement().toBrowseURL());
+			}
+		}
+	}
+
+	
+	/**
+	 * Split an interval. This means that we either change an existing point
+	 * to a number node or we add a number node. A new node may be added between
+	 * two other points or as a duplicate of one of them.
+	 * Depending on the reason for the split we use different methods to distribute
+	 * the existing numbers to the new intervals.
+	 * @param reason indicates the reason for the split
+	 * @return this if split was not done or a new {@link ExtNumbers} instance which is 
+	 * the start of a chain, the last instance in this chain points to the same
+	 * {@link ExtNumbers} instance as the {@code next} field in {@code this} .
+	 *  
+	 */
+	public ExtNumbers tryChange(int reason){
+		ExtNumbers en = this; 
+		if (reason == SR_FIX_ERROR){
+			if (notInOrder(Numbers.LEFT) == false && notInOrder(Numbers.RIGHT) == false){
+				if (badNum < 0 && worstHouse != null)
+					badNum = worstHouse.getHousenumber();
+				if (badNum > 0){
+					en = splitInterval();
+				}
+				else {
+					log.info("have to split",this);
+				}
+			}
+		} 
+		//TODO: in some cases it might be better to move a house to the prev or next interval instead of splitting
+		if (en == this)
+			en = tryAddNumberNode(reason);
+		boolean changedInterval = false;
+		if (en != this){
+			if (en.hasNumbers() && en.next != null && en.next.hasNumbers()){
+				changedInterval = true;
+			} else {
+				ExtNumbers test = en.hasNumbers() ?  en : en.next;
+				if (test.getNumbers().isSimilar(this.getNumbers()) == false)
+					changedInterval = true;
+			}
+			if (changedInterval)
+				housenumberRoad.setChanged(true);
+			else {
+				if (reason == SR_FIX_ERROR){
+					if (en.hasNumbers()){
+						en.worstHouse = worstHouse;
+						return en.tryChange(reason);
+					} else {
+						en.next.worstHouse = worstHouse;
+						en.next = en.next.tryAddNumberNode(reason);
+					}
+				}
+			}
+		}
+		return en;
+	}
+	
+	/**
+	 * Split an interval to remove overlaps 
+	 * 1) detect the optimal split position 
+	 * 2) calculate the new intervals
+	 * @return
+	 */
+	private ExtNumbers splitInterval(){
+		if (log.isDebugEnabled())
+			log.debug("trying to split",this,"so that",badNum,"is not contained");
+		boolean doSplit = false;
+		Numbers origNumbers = getNumbers();
+		if (origNumbers.countMatches(badNum) == 0){
+			if (log.isDebugEnabled())
+				log.debug("badNum",badNum,"is not contained in",this);
+			return this;
+		}
+		// create an test interval to find out which side contains the  bad number
+		Numbers testNumbers = new Numbers();
+		testNumbers.setNumbers(Numbers.LEFT, origNumbers.getLeftNumberStyle(), origNumbers.getLeftStart(), origNumbers.getLeftEnd());
+		boolean left = (testNumbers.countMatches(badNum) > 0);
+		
+		List<HousenumberMatch> before = new ArrayList<>();
+		List<HousenumberMatch> after = new ArrayList<>();
+		List<HousenumberMatch> toSplit = getHouses(left);
+		boolean inc = (origNumbers.getEnd(left) > origNumbers.getStart(left));
+		BitSet segmentsBefore = new BitSet();
+		BitSet segmentsAfter = new BitSet();
+		for (HousenumberMatch house : toSplit){
+			List<HousenumberMatch> target;
+			if (house.getHousenumber() < badNum){
+				target = inc ? before : after;
+			} else if (house.getHousenumber() > badNum){
+				target = inc ? after : before;
+			} else {
+				int s = origNumbers.getStart(left);
+				target = (s == badNum) ? before : after;
+			}
+			target.add(house);
+			if (target == before){
+				segmentsBefore.set(house.getSegment());
+			} else {
+				segmentsAfter.set(house.getSegment());
+			}
+		}
+		if (before.isEmpty() || after.isEmpty())
+			return this;
+		if (log.isDebugEnabled())
+			log.debug("todo: find best method to separate",before,"and",after);
+		HousenumberMatch house1 = before.get(before.size() - 1);
+		HousenumberMatch house2 = after.get(0);
+		List<HousenumberMatch> testOrder = new ArrayList<>(); 
+		testOrder.add(house1);
+		testOrder.add(house2);
+		Collections.sort(testOrder, new HousenumberMatchByPosComparator());
+		if (testOrder.get(0) != house1){
+			log.info("order indicates random case or missing road!",this);
+			housenumberRoad.setRandom(true);
+		}
+		int splitSegment = -1; 
+		if (house1.getSegment() != house2.getSegment()){
+			// simple case: change point
+			if (log.isDebugEnabled())
+				log.debug("simple case: change point to number node between",house1,house2);
+			// what point is best?, use beginning of 2nd for now 
+			splitSegment = house2.getSegment();
+			doSplit = true;
+			
+		} else {
+			int seg = house1.getSegment();
+			Coord c1 = getRoad().getPoints().get(seg);
+			Coord c2 = getRoad().getPoints().get(seg + 1);
+			double segmentLength = c1.distance(c2);
+
+			Coord toAdd = null;
+			boolean addOK = true;
+			double wantedFraction = (house1.getSegmentFrac() + house2.getSegmentFrac()) / 2;
+			// handle cases where perpendicular is not on the road
+			if (wantedFraction <= 0){
+				wantedFraction = 0;
+				toAdd = new Coord(c1);
+			} else if (wantedFraction >= 1){
+				wantedFraction = 1;
+				toAdd = new Coord(c2);
+			} 
+			double usedFraction = wantedFraction;
+
+			if (toAdd == null) {
+				Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
+				
+				toAdd = rasterLineNearPoint(c1, c2, wanted, true);
+				if (toAdd != null){
+					if (toAdd.equals(c1)){
+						toAdd = new Coord(c1);
+						usedFraction = 0.0;
+					}
+					else if (toAdd.equals(c2)){
+						toAdd = new Coord(c2);
+						usedFraction = 0;
+					}
+					else {
+						addOK = checkLineDistortion(c1, c2, toAdd);
+						if (addOK)
+							usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
+						else 
+							toAdd = null;
+					}
+				}
+			}
+			if (toAdd == null){
+				double len1 = wantedFraction * segmentLength;
+				double len2 = (1 - wantedFraction) * segmentLength;
+				if (Math.min(len1, len2) < MAX_LOCATE_ERROR){
+					if (len1 < len2){
+						toAdd = new Coord(c1);
+						usedFraction = 0.0;
+					} else {
+						toAdd = new Coord(c2);
+						usedFraction = 1.0;
+					}
+				}
+			}
+			if (toAdd == null){
+				log.error("internal error, cannot split",this);
+			}
+			if (toAdd != null){
+				if (log.isDebugEnabled())
+					log.debug("solution: split segment with length",formatLen(segmentLength),"at",formatLen(usedFraction * segmentLength));
+				doSplit = true;
+				splitSegment = seg+1;
+				addAsNumberNode(splitSegment, toAdd);
+				this.endInRoad++;
+				for (HousenumberMatch house : before){
+					if (house.getSegment() >= seg){
+						HousenumberGenerator.findClosestRoadSegment(house, getRoad(), seg, splitSegment);
+					}
+				}
+				for (HousenumberMatch house : after){
+					if (house.getSegment() < splitSegment)
+						HousenumberGenerator.findClosestRoadSegment(house, getRoad(), splitSegment, splitSegment + 1);
+					else 
+						house.setSegment(house.getSegment()+1);
+				}
+				recalcHousePositions(getHouses(!left)); // the other side
+			}
+		}
+		if (doSplit){
+			ExtNumbers en1 = split(splitSegment);
+			ExtNumbers en2 = en1.next;
+			if (en1.getHouses(Numbers.LEFT).size() + en2.getHouses(Numbers.LEFT).size() != getHouses(Numbers.LEFT).size() ||
+					en1.getHouses(Numbers.RIGHT).size() + en2.getHouses(Numbers.RIGHT).size() != getHouses(Numbers.RIGHT).size()){
+				log.error("internal error, lost houses");
+			}
+			log.info("number node added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());		
+			return en1;
+		}
+		return this;
+	}
+	
+	private boolean checkLineDistortion(Coord c1, Coord c2, Coord toAdd){
+		double distToLine = toAdd.getDisplayedCoord().distToLineSegment(c1.getDisplayedCoord(), c2.getDisplayedCoord());
+		if (distToLine > 0.2){
+			double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
+			if (Math.abs(angle) > 3){
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	/**
+	 * Try to add a number node.
+	 * We may change an existing point to a number node or add a new 
+	 * number node. A new node might be between the existing ones
+	 * or a duplicate of one of them.
+	 * @return
+	 */
+	private ExtNumbers tryAddNumberNode(int reason) {
+		String action;
+		if (endInRoad - startInRoad > 1)
+			action = "change";
+		else {
+			if (getRoad().getPoints().size() + 1 > LineSplitterFilter.MAX_POINTS_IN_LINE){
+				log.warn("can't change intervals, road has already",LineSplitterFilter.MAX_POINTS_IN_LINE,"points");
+				return this; // can't add a node
+			}
+			Coord c1 = getRoad().getPoints().get(startInRoad);
+			Coord c2 = getRoad().getPoints().get(startInRoad+1);
+			if (c1.equals(c2)){
+				return dupNode(0, SR_FIX_ERROR);
+			} 
+			double segmentLength = c1.distance(c2);
+			int countAfterEnd = 0, countBeforeStart = 0;
+			double minFraction0To1 = 2;
+			double maxFraction0To1 = -1;
+			for (int side = 0; side < 2; side++){
+				boolean left = side == 0;
+				List<HousenumberMatch> houses = getHouses(left);
+				for (HousenumberMatch house : houses){
+					if (house.getSegmentFrac() < 0)
+						++countBeforeStart;
+					else if (house.getSegmentFrac() > 1)
+						++countAfterEnd;
+					else {
+						if (minFraction0To1 > house.getSegmentFrac())
+							minFraction0To1 = house.getSegmentFrac();
+						if (maxFraction0To1 < house.getSegmentFrac())
+							maxFraction0To1 = house.getSegmentFrac();
+					}
+				}
+			}
+			// special cases: perpendicular not on the road
+			if (countBeforeStart > 0){
+				return dupNode(0, SR_SPLIT_ROAD_END);
+			}
+			if (countAfterEnd > 0){
+				return dupNode(1, SR_SPLIT_ROAD_END);
+			}
+			
+			// try to find a good split point depending on the split reason
+			double wantedFraction, midFraction;
+			wantedFraction = midFraction = (minFraction0To1 + maxFraction0To1) / 2;
+			Coord toAdd = null;  
+			double len1 = segmentLength * minFraction0To1; // dist to first 
+			double len2 = segmentLength * maxFraction0To1; 
+			double len3 = (1-maxFraction0To1) * segmentLength;
+			double expectedError = c1.getDisplayedCoord().distance(new Coord(c1.getLatitude()+1,c1.getLongitude()));
+			double maxDistBefore = expectedError;
+			double maxDistAfter = expectedError;
+			if (reason == SR_FIX_ERROR && worstHouse != null){
+				wantedFraction = worstHouse.getSegmentFrac();
+				if (wantedFraction < minFraction0To1 || wantedFraction > maxFraction0To1){
+					log.error("internal error, worst house not found",this,worstHouse);
+				}
+			}
+			boolean allowSplitBetween = true;
+			boolean forceEmpty = false;
+			if (reason == SR_OPT_LEN){
+				if (log.isDebugEnabled()){
+					if (maxFraction0To1 != minFraction0To1){
+						log.debug("trying to find good split point, houses are between",formatLen(len1),"and",formatLen(len2),"in segment with",formatLen(segmentLength));
+					} else 
+						log.debug("trying to find good split point, houses are at",formatLen(len1),"in segment with",formatLen(segmentLength));
+				}
+				if (len2 - len1 < 10 && getHouses(Numbers.LEFT).size() <= 1 && getHouses(Numbers.RIGHT).size() <= 1){
+					// one house or two opposite houses  
+					// we try to split so that the house(s) are near the middle of one part
+					wantedFraction = wantedFraction * 2 - (wantedFraction > 0.5 ? 1 : 0);
+					allowSplitBetween = false;
+				} else {
+					if (len1 > MAX_LOCATE_ERROR / 2){
+						// create empty segment at start
+						wantedFraction = minFraction0To1 * 0.999;  
+						forceEmpty = true;
+					} 
+					if (len3 > MAX_LOCATE_ERROR / 2 && len3 > len1){
+						// create empty segment at end
+						wantedFraction = maxFraction0To1 * 1.001;
+						forceEmpty = true;
+					}
+				}
+			}
+			double partLen = wantedFraction * segmentLength ;
+			double shorterLen = Math.min(partLen , segmentLength - partLen);
+			if (shorterLen < 10){
+				if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
+					return dupNode(midFraction, SR_FIX_ERROR);
+				double splitFrac = len1 < len3 ? minFraction0To1 : maxFraction0To1;
+				return dupNode(splitFrac, SR_OPT_LEN);
+			}
+			double usedFraction = 0;
+			double bestDist = Double.MAX_VALUE;
+			if (wantedFraction < minFraction0To1){
+				maxDistAfter = 0;
+			}
+			if (wantedFraction > maxFraction0To1){
+				maxDistBefore = 0;
+			}
+			for (;;){
+				Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
+				Map<Double, List<Coord>> candidates = rasterLineNearPoint2(c1, c2, wanted, maxDistBefore, maxDistAfter);
+				boolean foundGood = false;
+				for (Entry<Double, List<Coord>> entry : candidates.entrySet()){
+					if (foundGood)
+						break;
+					bestDist = entry.getKey();
+					for (Coord candidate: entry.getValue()){
+						toAdd = candidate;
+						usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
+						if (usedFraction <= 0 || usedFraction >= 1)
+							toAdd = null;
+						else if (usedFraction > minFraction0To1 && wantedFraction < minFraction0To1 || usedFraction < maxFraction0To1 && wantedFraction > maxFraction0To1){
+							toAdd = null;
+						} else if (allowSplitBetween == false && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1){
+							toAdd = null;
+						} else {
+							if (bestDist > 0.2){
+								double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
+								if (Math.abs(angle) > 3){
+									toAdd = null;
+									continue;
+								}
+							}
+							foundGood = true;
+							break;
+						}
+					}
+				}
+				if (foundGood){
+					break;
+				}
+				toAdd = null;
+				boolean tryAgain = false;
+				if (maxDistBefore > 0 && maxDistBefore < segmentLength * wantedFraction) {
+					maxDistBefore *= 2;
+					tryAgain = true;
+				}
+				if (maxDistAfter > 0 && maxDistAfter < segmentLength * (1 - wantedFraction)) {
+					maxDistAfter *= 2;
+					tryAgain = true;
+				}
+				if (!tryAgain)
+					break;
+			} 			
+			
+			boolean addOK = true;
+			if (toAdd == null)
+				addOK = false;
+			else {
+				toAdd.incHighwayCount();
+				if (log.isDebugEnabled()){
+					log.debug("spliting road segment",startInRoad,"at",formatLen(usedFraction * segmentLength));
+				}
+			}
+			if (!addOK){
+				if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
+					return dupNode(midFraction, SR_FIX_ERROR);
+				if (Math.min(len1, len3) < MAX_LOCATE_ERROR ){
+					double splitFrac = -1;
+					if (reason == SR_OPT_LEN){
+						if (wantedFraction <= minFraction0To1)
+							splitFrac = minFraction0To1;
+						else if (wantedFraction >= maxFraction0To1)
+							splitFrac = maxFraction0To1;
+						if (splitFrac <= 0.5 && len1 >= MAX_LOCATE_ERROR || splitFrac > 0.5 && len3 >= MAX_LOCATE_ERROR){
+							splitFrac = -1;
+						}
+					}
+					if (splitFrac < 0)
+						splitFrac = (minFraction0To1 != maxFraction0To1) ? midFraction : minFraction0To1;
+					return dupNode(splitFrac, SR_OPT_LEN);
+				}
+				if(reason == SR_FIX_ERROR)
+					log.warn("can't fix error in interval",this);
+				else if (log.isDebugEnabled())
+					log.debug("can't improve search result",this);
+				return this;
+			}
+			if (log.isInfoEnabled())
+				log.info("adding number node at",toAdd.toDegreeString(),"to split, dist to line is",formatLen(bestDist));
+			action = "add";
+			this.endInRoad = addAsNumberNode(startInRoad + 1, toAdd);
+			int forcedSegment = - 1;
+			if (forceEmpty){
+				if (wantedFraction < minFraction0To1)
+					forcedSegment = startInRoad + 1;
+				else if (wantedFraction > maxFraction0To1)
+					forcedSegment = startInRoad;
+			}
+			if (forcedSegment >= 0){
+				setSegment(forcedSegment, getHouses(Numbers.LEFT));
+				setSegment(forcedSegment, getHouses(Numbers.RIGHT));
+			} else {
+				this.recalcHousePositions(getHouses(Numbers.LEFT));
+				this.recalcHousePositions(getHouses(Numbers.RIGHT));
+			}
+		}
+		int splitSegment = (startInRoad + endInRoad) / 2;
+		if (worstHouse != null){
+			if (worstHouse.getSegment() == startInRoad)
+				splitSegment = startInRoad + 1;
+			else if (worstHouse.getSegment() == endInRoad - 1)
+				splitSegment = worstHouse.getSegment();
+		} else if (endInRoad - startInRoad > 2){
+			int firstSegWithHouses = endInRoad;
+			int lastSegWithHouses = -1;
+			for (int side = 0; side < 2; side++){
+				boolean left = side == 0;
+				List<HousenumberMatch> houses = getHouses(left);
+				for (HousenumberMatch house : houses){
+					int s = house.getSegment();
+					if (s < firstSegWithHouses)
+						firstSegWithHouses = s;
+					if (s > lastSegWithHouses)
+						lastSegWithHouses = s;
+				}
+					
+			}
+			splitSegment = (firstSegWithHouses + lastSegWithHouses) / 2;
+			if (splitSegment == startInRoad)
+				splitSegment++;
+		}
+		ExtNumbers en1 = split(splitSegment);
+		ExtNumbers en2 = en1.next;
+		if (reason == SR_OPT_LEN){
+			// TODO: fill gaps, e.g. if split results in O,1,9 -> O,1,1 + O,9,9 ?
+		}
+		if ("add".equals(action))
+			log.info("number node added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
+		else 
+			log.info("point changed to number node in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
+		return en1;
+	}
+
+
+	/**
+	 * Duplicate a node in the road. This creates a zero-length segment.
+	 * We can add house numbers to this segment and the position in the 
+	 * address search will be the same for all of them.
+	 * @param fraction a value below 0.5 means duplicate the start node, others 
+	 * duplicate the end node 
+	 * @return the new chain with two segments 
+	 */
+	private ExtNumbers dupNode(double fraction, int reason) {
+		log.info("duplicating number node in road",getRoad(),getNumbers(),getHouses(Numbers.LEFT),getHouses(Numbers.RIGHT));
+		boolean atStart = (fraction <= 0.5);
+		
+		// add a copy of an existing node 
+		int index = (atStart) ? startInRoad : endInRoad;
+		int splitSegment = (atStart) ? startInRoad + 1: endInRoad;
+		Coord closePoint = getRoad().getPoints().get(index);
+		Coord toAdd = new Coord(closePoint);
+		toAdd.setOnBoundary(closePoint.getOnBoundary());
+		toAdd.incHighwayCount();
+		// we have to make sure that the road starts and ends with a CoordNode!
+		this.endInRoad = addAsNumberNode(splitSegment, toAdd);
+		
+		// distribute the houses to the new intervals
+		List<ArrayList<HousenumberMatch>> leftTargets = Arrays.asList(new ArrayList<HousenumberMatch>(),new ArrayList<HousenumberMatch>());
+		List<ArrayList<HousenumberMatch>> rightTargets = Arrays.asList(new ArrayList<HousenumberMatch>(),new ArrayList<HousenumberMatch>());
+		int target;
+		if (reason == SR_SPLIT_ROAD_END || reason == SR_OPT_LEN){
+			for (int side = 0; side < 2; side++){
+				boolean left = side == 0;
+				List<ArrayList<HousenumberMatch>> targets = left ? leftTargets : rightTargets;
+				for (HousenumberMatch house : getHouses(left)){
+					if (house.getSegmentFrac() < fraction)
+						target = 0;
+					else if (house.getSegmentFrac() > fraction)
+						target = 1;
+					else target = (atStart) ? 0 : 1;
+					targets.get(target).add(house);
+				}
+			}
+		} else if (getHouses(Numbers.LEFT).size() > 1 || getHouses(Numbers.RIGHT).size() > 1){
+			int start,end;
+			for (int side = 0; side < 2; side++){
+				boolean left = side == 0;
+				if (getHouses(left).isEmpty())
+					continue;
+				start = getNumbers().getStart(left);
+				end = getNumbers().getEnd(left);
+				List<ArrayList<HousenumberMatch>> targets = left ? leftTargets : rightTargets;
+				if (start != end){
+					int midNum = (start + end) / 2;
+					for (HousenumberMatch house : getHouses(left)){
+						if (house.getHousenumber() < midNum)
+							target = 0;
+						else if (house.getHousenumber() > midNum)
+							target = 1;
+						else target = (atStart) ? 0 : 1;
+						targets.get(target).add(house);
+					}
+				} else if (multipleZipOrCity(left) == false){
+					if (atStart) 
+						targets.get(1).addAll(getHouses(left));
+					else 
+						targets.get(0).addAll(getHouses(left));
+				} else {
+					int mid = getHouses(left).size() / 2;
+					targets.get(0).addAll(getHouses(left).subList(0, mid));
+					targets.get(1).addAll(getHouses(left).subList(mid,getHouses(left).size()));
+				}
+				
+			}
+		} else {
+			log.error("internal error, don't know how to split", this); 
+		}
+		
+		assert splitSegment != startInRoad && splitSegment != endInRoad;
+		// make sure that the numbers are assigned to the wanted segment
+		setSegment(startInRoad, leftTargets.get(0));
+		setSegment(startInRoad, rightTargets.get(0));
+		setSegment(splitSegment, leftTargets.get(1));
+		setSegment(splitSegment, rightTargets.get(1));
+		
+		// don't use split() here, the numbers in this may not be properly
+		// sorted and we don't want to sort them
+		ExtNumbers en1 = divide();
+		ExtNumbers en2 = en1.next;
+		en1.setNumbers(leftTargets.get(0), startInRoad, splitSegment, true);
+		en1.setNumbers(rightTargets.get(0), startInRoad, splitSegment, false);
+		en2.setNumbers(leftTargets.get(1), splitSegment, endInRoad, true);
+		en2.setNumbers(rightTargets.get(1), splitSegment, endInRoad, false);
+		
+		log.info("zero length interval added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
+		if (atStart && !en1.hasNumbers() ||  !atStart && !en2.hasNumbers()){
+			log.error("internal error, zero length interval has no numbers in road",getRoad());
+		}
+		return en1;	
+		
+	}
+
+	private void setSegment(int segment, List<HousenumberMatch> houses) {
+		for (HousenumberMatch house : houses){
+			HousenumberGenerator.findClosestRoadSegment(house, getRoad(), segment,segment+1);
+			if (house.getRoad() == null || house.getSegment() != segment){
+				// should not happen
+				log.error("internal error, house too far from forced segment in road",getRoad(),house,house.getElement().toBrowseURL());
+				house.setIgnored(true);
+			}
+		}
+		
+	}
+
+	/**
+	 * This should be called if a point was added to the road segment
+	 * covered by this interval. We have to recalculate the segment numbers
+	 * and fraction values.
+	 */
+	private void recalcHousePositions(List<HousenumberMatch> houses){
+		for (HousenumberMatch house : houses){
+			HousenumberGenerator.findClosestRoadSegment(house, getRoad(), startInRoad, endInRoad);
+		}
+		if (houses.size() > 1)
+			Collections.sort(houses, new HousenumberMatchByPosComparator());
+	}
+	
+	
+	/**
+	 * Create two empty intervals which will replace this.
+	 * @return
+	 */
+	private ExtNumbers divide(){
+		ExtNumbers en1 = new ExtNumbers(housenumberRoad);
+		ExtNumbers en2 = new ExtNumbers(housenumberRoad);
+		// maintain the linked list
+		en1.prev = this.prev;
+		if (prev != null)
+			prev.next = en1;
+		en1.next = en2;
+		en2.prev = en1;
+		en2.next = this.next;
+		if (this.next != null)
+			next.prev = en2;
+		en1.setNodeIndex(nodeIndex);
+		en2.setNodeIndex(nodeIndex+1);
+		ExtNumbers work = en2.next;
+		while (work != null){
+			work.setNodeIndex(work.nodeIndex + 1);
+			work = work.next;
+		}
+		return en1;
+	}
+	
+	/**
+	 * Add node to the road.
+	 * Maintain the positions of houses in the following intervals.
+	 * @param toAdd 
+	 * @param pos 
+	 * @return new start of next interval
+	 */
+	private int addAsNumberNode(int pos, Coord toAdd){
+		toAdd.setNumberNode(true);
+		getRoad().getPoints().add(pos, toAdd);
+		
+		ExtNumbers work = next;
+		while (work != null){
+			work.increaseNodeIndexes(startInRoad); 			
+			work = work.next;
+		}
+		return endInRoad + 1;
+	}
+	
+	/**
+	 * This has to be called when a point is added to the road.
+	 * @param startPos
+	 */
+	private void increaseNodeIndexes(int startPos){
+		if (hasNumbers() == false)
+			return;
+		if (startInRoad > startPos){
+			startInRoad++;
+			endInRoad++;
+		}
+		for (int side = 0; side < 2; side++){
+			boolean left = side == 0;
+			for (HousenumberMatch house : getHouses(left)){
+				int s = house.getSegment();
+				if (s > startPos)
+					house.setSegment(s+1);
+				else 
+					assert false : "internal error " + getRoad() + " " + getHouses(Numbers.LEFT) + " " + getHouses(Numbers.RIGHT);
+			}
+		}
+	}
+	
+	
+	private void findGoodSplitPos(){
+		badNum = -1;
+		worstHouse = null;
+		boolean multipleZipOrCity = false;
+		for (int side = 0; side < 2; side++){
+			boolean left = side == 0;
+			List<HousenumberMatch> houses = getHouses(left);
+			if (houses.size() <= 1)
+				continue;
+			if (multipleZipOrCity(left))
+				multipleZipOrCity = true;
+			for (HousenumberMatch house: houses){
+				int hn = house.getHousenumber();
+				if (countOccurence(houses, hn) > 1)
+					continue;
+				ExtNumbers modIvl = simulateRemovalOfHouseNumber(hn, left);
+				if (modIvl.isPlausible()){
+					badNum = hn;
+					if (log.isDebugEnabled())
+						log.debug("splitpos details: single remove of",badNum,"results in plausible interval");
+					return;
+				}
+			}
+		}
+		if (multipleZipOrCity)
+			return; // unlikely
+//		log.debug("did not yet find good split position");
+		Numbers ivl = getNumbers();
+		int[] firstBad = {-1,-1};
+		int[] lastBad = {-1,-1};
+		for (int side = 0; side < 2; side++){
+			boolean left = (side == 0);
+			int step = 2;
+			if (ivl.getNumberStyle(left) == NumberStyle.BOTH)
+				step = 1;
+			int s = ivl.getStart(left);
+			int e = ivl.getEnd(left);
+			int s2 = ivl.getStart(!left);
+			int e2 = ivl.getEnd(!left );
+			NumberStyle style2 = ivl.getNumberStyle(!left); 
+			for (int hn = Math.min(s, e); hn <= Math.max(s, e); hn += step){
+				if (style2 == NumberStyle.EVEN && hn % 2 == 1 || style2 == NumberStyle.ODD && hn % 2 == 0 ){
+					if (firstBad[side] < 0)
+						firstBad[side] = hn;
+					lastBad[side] = hn;
+					continue;
+				}
+				if (hn < Math.min(s2, e2) || hn > Math.max(s2, e2)){
+					if (firstBad[side] < 0)
+						firstBad[side] = hn;
+					lastBad[side] = hn;
+				}
+			}
+		}
+		if (firstBad[0] == lastBad[0]){
+			badNum = firstBad[0];
+			if (badNum >= 0)
+				return;
+		}
+		if (firstBad[1] == lastBad[1]){
+			badNum = firstBad[1];
+			if (badNum >= 0)
+				return;
+		}
+		badNum = Math.max(firstBad[0], lastBad[0]);
+		if (badNum == -1)
+			badNum = Math.min(firstBad[1], lastBad[1]);
+		if (log.isDebugEnabled())
+			log.debug("splitpos details",Arrays.toString(firstBad), Arrays.toString(lastBad),"gives badNum",badNum);
+	}
+	
+	private boolean multipleZipOrCity(boolean left) {
+		RoadSide rs = left ? leftSide : rightSide;
+		return rs == null ? false : rs.multipleCities || rs.multipleZipCodes;
+	}
+
+	public ExtNumbers checkChainPlausibility(String streetName,
+			List<HousenumberMatch> potentialNumbersThisRoad) {
+		// we try to repair up to 10 times
+		ExtNumbers head = this;
+		for (int loop = 0; loop < 10; loop++){
+			boolean anyChanges = false;
+			for (ExtNumbers en1 = head; en1 != null; en1 = en1.next){
+				if (anyChanges)
+					break;
+				if (en1.hasNumbers() == false)
+					continue;
+				for (ExtNumbers en2 = en1.next; en2 != null; en2 = en2.next){
+					if (anyChanges)
+						break;
+					if (en2.hasNumbers() == false)
+						continue;
+					
+					int res = checkIntervals(streetName, en1, en2);
+					switch (res) {
+					case OK_NO_CHANGES:
+					case NOT_OK_KEEP:
+						break;
+					case OK_AFTER_CHANGES:
+						anyChanges = true;
+						break;
+					case NOT_OK_TRY_SPLIT:
+						if (en1.needsSplit){
+							ExtNumbers test = en1.tryChange(SR_FIX_ERROR);
+							if (test != en1){
+								housenumberRoad.setChanged(true);
+								anyChanges = true;
+								if (test.prev == null){
+									head = test;
+								}
+							}
+						}
+						if (en2.needsSplit){
+							ExtNumbers test = en2.tryChange(SR_FIX_ERROR);
+							if (test != en2){
+								anyChanges = true;
+								housenumberRoad.setChanged(true);
+							}
+						}
+						break;
+					case NOT_OK_STOP:
+						return head;
+					default:
+						break;
+					}
+				}
+			}
+			if (!anyChanges)
+				break;
+		}
+		return head;
+	}
+	
+	public static final int OK_NO_CHANGES = 0;
+	public static final int OK_AFTER_CHANGES = 1;
+	public static final int NOT_OK_TRY_SPLIT = 2;
+	public static final int NOT_OK_KEEP  = 3;
+	public static final int NOT_OK_STOP = 4;
+
+	/**
+	 * Check if two intervals are overlapping  (all combinations of left + right)
+	 * @param streetName
+	 * @param en1
+	 * @param en2
+	 * @return true if something was changed
+	 */
+	public static int checkIntervals(String streetName, ExtNumbers en1, ExtNumbers en2) {
+		if (en1.getRoad() != en2.getRoad()){
+			Coord cs1 = en1.getRoad().getPoints().get(en1.startInRoad);
+			Coord ce1 = en1.getRoad().getPoints().get(en1.endInRoad);
+			Coord ce2 = en2.getRoad().getPoints().get(en2.endInRoad);
+			if (ce2 == cs1 || ce2 == ce1){
+				ExtNumbers help = en1;
+				en1 = en2;
+				en2 = help;
+			}
+		}
+		boolean allOK = true;
+		Numbers ivl1 = en1.getNumbers();
+		Numbers ivl2 = en2.getNumbers();
+		for (int i = 0; i < 2; i++){
+			boolean left1 = i == 0;
+			NumberStyle style1 = ivl1.getNumberStyle(left1);
+			if (style1 == NumberStyle.NONE)
+				continue;
+			int s1 = ivl1.getStart(left1);
+			int e1 = ivl1.getEnd(left1);
+			for (int j = 0; j < 2; j++){
+				boolean left2 = (j == 0);
+				NumberStyle style2 = ivl2.getNumberStyle(left2);
+				if (style2 == NumberStyle.NONE)
+					continue;
+				int s2 = ivl2.getStart(left2);
+				int e2 = ivl2.getEnd(left2);
+				boolean ok = true;
+				if (style1 == style2 || style1 == NumberStyle.BOTH || style2 == NumberStyle.BOTH)
+					ok = checkIntervalBoundaries(s1, e1, s2, e2, left1 == left2 && en1.getRoad() == en2.getRoad());
+				if (ok) 
+					continue;
+				if (en1.getRoad() != en2.getRoad() && en1.hasGaps == false && en2.hasGaps == false){
+					allOK = false;
+					continue;
+				}
+
+				if (s1 == e1){
+					if (en1.getHouses(left1).get(0).isFarDuplicate()){
+						allOK = false;
+						continue;
+					}
+				}
+				if (s2 == e2){
+					if (en2.getHouses(left2).get(0).isFarDuplicate()){
+						allOK = false;
+						continue;
+					}
+				}
+				List<HousenumberMatch> houses1 = en1.getHouses(left1);
+				List<HousenumberMatch> houses2 = en2.getHouses(left2);
+				if (log.isInfoEnabled()){
+					log.info("detected unplausible combination of intervals in",streetName, 
+							s1 + ".." + e1, "and", s2 + ".." + e2, "houses:",
+							(left1 ? "left:" : "right"), houses1, 
+							(left2 ? "left:" : "right"), houses2,
+							(en1.getRoad() == en2.getRoad() ? "in road " + en1.getRoad() :  
+								"road id(s):" + en1.getRoad().getRoadDef().getId() + ", " + en2.getRoad().getRoadDef().getId()));
+				}
+				double smallestDelta = Double.POSITIVE_INFINITY;
+				HousenumberMatch bestMoveOrig = null;
+				HousenumberMatch bestMoveMod = null;
+				ExtNumbers bestRemove = null;
+				List<HousenumberMatch> possibleRemoves1 = new ArrayList<>(); 
+				List<HousenumberMatch> possibleRemoves2 = new ArrayList<>();
+				
+				if (en1.housenumberRoad.isRandom() == false && en2.housenumberRoad.isRandom() == false){
+					// check if we can move a house from en1 to en2
+					for (HousenumberMatch house : houses1){
+						if (house.getGroup() != null)
+							continue;
+						int n = house.getHousenumber();
+						if (countOccurence(houses1, n) > 1)
+							continue;
+
+						if (n == s1 || n == e1) {
+							Numbers modNumbers = en1.simulateRemovalOfHouseNumber(n, left1).getNumbers();
+							int s1Mod = modNumbers.getStart(left1);
+							int e1Mod = modNumbers.getEnd(left1);
+							NumberStyle modStyle = modNumbers.getNumberStyle(left1);
+							boolean ok2 = true;
+							if (modStyle == style2 || modStyle == NumberStyle.BOTH || style2 == NumberStyle.BOTH)
+								ok2 = checkIntervalBoundaries(s1Mod, e1Mod, s2, e2, left1 == left2 && en1.getRoad() == en2.getRoad());
+							if (ok2){
+								// the intervals don't overlap if house is removed from en1
+								if (houses1.size() > 1)
+									possibleRemoves1.add(house);
+								
+								// check if it fits into en2
+								HousenumberMatch test = checkMoveTo(house, en2, left2);
+								if (test.getRoad() != null){
+									double deltaDist = test.getDistance() - house.getDistance(); 
+									if (deltaDist < smallestDelta){
+										bestMoveMod = test;
+										bestMoveOrig = house;
+										smallestDelta = deltaDist;
+										bestRemove = en1;
+									}
+								} 
+							}
+						}
+					}
+					for (HousenumberMatch house : houses2){
+						if (house.getGroup() != null)
+							continue;
+						int n = house.getHousenumber();
+						if (countOccurence(houses2, n) > 1)
+							continue;
+
+						if (n == s2 || n == e2) {
+							Numbers modNumbers = en2.simulateRemovalOfHouseNumber(n, left2).getNumbers();
+							int s2Mod = modNumbers.getStart(left2);
+							int e2Mod = modNumbers.getEnd(left2);
+							NumberStyle modStyle = modNumbers.getNumberStyle(left2);
+							boolean ok2 = true;
+							if (modStyle == style1 || modStyle == NumberStyle.BOTH || style1 == NumberStyle.BOTH)
+								ok2 = checkIntervalBoundaries(s1, e1, s2Mod, e2Mod, left1 == left2 && en1.getRoad() == en2.getRoad());
+							if (ok2){
+								// the intervals don't overlap if house is removed from en2
+								if (houses2.size() > 1)
+									possibleRemoves2.add(house);
+								
+								
+								// check if it fits into en1
+								HousenumberMatch test = checkMoveTo(house, en1, left1);
+								if (test.getRoad() != null){
+									double deltaDist = test.getDistance() - house.getDistance(); 
+									if (deltaDist < smallestDelta){
+										bestMoveMod = test;
+										bestMoveOrig = house;
+										smallestDelta = deltaDist;
+										bestRemove = en2;
+									}
+								} 								
+							}
+						}
+					}
+					if (bestMoveMod != null){
+						if (bestMoveOrig.isDuplicate()){
+							log.warn("duplicate number causes problems",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL() );
+						}
+						List<HousenumberMatch> fromHouses, toHouses;
+						ExtNumbers from,to;
+						if (bestRemove == en1){
+							from = en1;
+							to = en2;
+							fromHouses = houses1;
+							toHouses = houses2;
+							bestMoveOrig.setLeft(left2);
+
+						} else {
+							from = en2;
+							to = en1;
+							fromHouses = houses2;
+							toHouses = houses1;
+							bestMoveOrig.setLeft(left1);
+						}
+						if (bestMoveOrig.getMoved() >= 3){
+							bestMoveMod = null;
+							bestMoveOrig = null;
+							bestRemove.housenumberRoad.setRandom(true);
+						} else {
+							if (log.isInfoEnabled()){
+								if (to.getRoad() == from.getRoad())
+									log.info("moving",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL(),"from",fromHouses,"to",toHouses,"in road",to.getRoad());
+								else 
+									log.info("moving",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL(),"from",fromHouses,"in road",from.getRoad(),"to",toHouses,"in road",to.getRoad());
+							}
+							bestMoveOrig.incMoved();
+							bestMoveOrig.setRoad(to.getRoad());
+							bestMoveOrig.setHousenumberRoad(to.housenumberRoad);
+							bestMoveOrig.setSegment(bestMoveMod.getSegment());
+							bestMoveOrig.setDistance(bestMoveMod.getDistance());
+							bestMoveOrig.setSegmentFrac(bestMoveMod.getSegmentFrac());
+							from.housenumberRoad.getHouses().remove(bestMoveOrig);
+							fromHouses.remove(bestMoveOrig);
+							toHouses.add(bestMoveOrig);
+							Collections.sort(toHouses, new HousenumberMatchByPosComparator());
+							en1.reset();
+							en2.reset();
+							en1.setNumbers(houses1, en1.startInRoad, en1.endInRoad, left1);
+							en2.setNumbers(houses2, en2.startInRoad, en2.endInRoad, left2);
+							return OK_AFTER_CHANGES;
+						}
+					} 
+				}
+				ExtNumbers toSplit = null;
+				int splitNum = -1;
+				int delta1 = Math.abs(e1-s1);
+				int delta2 = Math.abs(e2-s2);
+				if (delta1 > 0 && delta2 > 0){
+					if (en1.hasGaps != en2.hasGaps){
+						if (en1.hasGaps){
+							if (possibleRemoves1.isEmpty() == false)
+								splitNum = possibleRemoves1.get(0).getHousenumber();
+							toSplit = en1;
+						} else {
+							if (possibleRemoves2.isEmpty() == false)
+								splitNum = possibleRemoves2.get(0).getHousenumber();
+							toSplit = en2;
+						}
+					} else if (possibleRemoves1.size() == 1){
+						splitNum = possibleRemoves1.get(0).getHousenumber();
+						toSplit = en1;
+					} else if (possibleRemoves2.size() == 1){
+						splitNum = possibleRemoves2.get(0).getHousenumber();
+						toSplit = en2;
+					} else if (possibleRemoves1.size() > 0){
+						splitNum = possibleRemoves1.get(0).getHousenumber();
+						toSplit = en1;
+					} else if (possibleRemoves2.size() > 0){
+						splitNum = possibleRemoves2.get(0).getHousenumber();
+						toSplit = en2;
+					} else {
+						// intervals are overlapping, a single remove doesn't help
+						if (ivl1.isContained(s2, left1) && ivl1.isContained(e2, left1)){
+							// en2 is completely in en1
+							toSplit = en1;
+							splitNum = s2;
+						} else if (ivl2.isContained(s1, left2) && ivl2.isContained(e1, left2)){
+							// en1 is completely in en2
+							toSplit = en2;
+							splitNum = s1;
+						} 
+						else {
+							if (ivl1.isContained(s2, left1)){
+								toSplit = en1;
+								splitNum = s2;
+							} else if (ivl1.isContained(e2, left1)){
+								toSplit = en1;
+								splitNum = e2;
+							} else if (ivl2.isContained(s1, left2)){
+								toSplit = en2;
+								splitNum = s1;
+							} else if (ivl2.isContained(e1, left2)){
+								toSplit = en2;
+								splitNum = e1;
+							} else if (style1 == NumberStyle.BOTH){
+								toSplit = en1;
+							} else if (style2 == NumberStyle.BOTH){
+								toSplit = en2;
+							} else {
+								toSplit = (delta1 >= delta2) ? en1 : en2;
+							}
+						}
+					}
+				}
+				else if (delta1 == 0 && delta2 > 0 && countOccurence(houses2, s1) == 0){
+					toSplit = en2;
+					splitNum = s1;
+				}
+				else if (delta2 == 0 && delta1 > 0 && countOccurence(houses1, s2) == 0){ 
+					toSplit = en1;
+					splitNum = s2;
+				}
+				if (toSplit != null){
+					toSplit.worstHouse = null;
+					toSplit.badNum = splitNum;
+					toSplit.setNeedsSplit(true);
+					return NOT_OK_TRY_SPLIT;
+					
+				}
+				allOK = false;
+			}
+		}
+		if (allOK)
+			return OK_NO_CHANGES;
+		return NOT_OK_KEEP;
+	}
+
+	
+	private static HousenumberMatch checkMoveTo(HousenumberMatch house, ExtNumbers other, boolean otherLeft){
+		HousenumberMatch test = new HousenumberMatch(house);
+		Numbers otherIvl = other.getNumbers();
+		int oStart = otherIvl.getStart(otherLeft);
+		int oEnd = otherIvl.getEnd(otherLeft);
+		// check if it fits into en2
+		if (house.getHousenumber() <= Math.min(oStart, oEnd) ||house.getHousenumber() >= Math.max(oStart, oEnd))
+			return test;
+		boolean even = house.getHousenumber() % 2 == 0;
+		NumberStyle oStyle = otherIvl.getNumberStyle(otherLeft);
+		if (oStyle == NumberStyle.EVEN && !even || oStyle == NumberStyle.ODD && even)
+			return test;
+		HousenumberGenerator.findClosestRoadSegment(test, other.getRoad(), other.startInRoad, other.endInRoad);
+		
+		if (test.getDistance() <= HousenumberGenerator.MAX_DISTANCE_TO_ROAD){
+			Coord c1 = other.getRoad().getPoints().get(test.getSegment());
+			Coord c2 = other.getRoad().getPoints().get(test.getSegment() + 1);
+			if (c1.highPrecEquals(c2) || otherLeft == HousenumberGenerator.isLeft(c1, c2, house.getLocation())){
+				test.setLeft(otherLeft);
+				return test;
+			}
+		}
+		test.setRoad(null);
+		return test;
+	} 	
+	
+	/**
+	 * Check the start and end values of two consecutive number intervals
+	 * for plausibility.
+	 * @param s1 1st interval start
+	 * @param e1 1st interval end
+	 * @param s2 2nd interval start
+	 * @param e2 2nd interval end
+	 * @return
+	 */
+	private static boolean checkIntervalBoundaries(int s1, int e1, int s2,int e2, boolean sameSide){
+		boolean ok = false;
+		// many cases, maybe someone finds simpler code?
+		if (sameSide){
+			// allow equal numbers at boundaries
+			if (s1 == e1) {
+				if (e1 == s2) ok = true; // 6 6 6 4 , 6 6 6 6 , 6 6 6 8 
+				else if (e1 < s2 && e1 < e2) ok = true; // 4 4 8 6, 4 4 6 8 1st equal, not in higher 2nd 
+				else if (e1 > s2 && e1 > e2) ok = true; // 6 6 4 2, 6 6 2 4 1st equal, not in lower 2nd
+			} else if (s1 < e1){
+				if (e1 <= s2 && s2 <= e2) ok = true; // 6 8 8 8, 6 8 8 10, 6 8 10 10, 6 8 10 12 up
+				else if (s2 > e2 && e1 < e2) ok = true; // 6 8 12 10 up down, no overlap, 2nd is higher   
+				else if (s1 > s2 && s1 > e2) ok = true; // 6 8 4 4, 6 8 4 2, 6 8 2 4 up down, 2nd is lower
+			} else { // s1 > e1
+				if (e1 >= s2 && s2 >= e2) ok = true;  // 8 6 6 6, 8 6 6 4, 8 6 4 4, 8 6 4 2 down
+				else if (e1 > s2 && e1 > e2) ok = true;  // 8 6 2 4 down up, no overlap,2nd is lower 
+				else if (s1 < s2 && s1 < e2) ok = true;  // 8 6 10 10, 8 6 10 12, 8 6 12 10, 1st down, no overlap,2nd is higher  
+			}
+		} else {
+			// left and right: don't allow equal numbers in different intervals
+			if (s1 == e1) {
+				if (s2 == e2 && s1 != s2) ok = true; // 6 6 2 2, 6 6 8 8  
+				else if (s2 < e2 && (s1 < s2 || s1 > e2)) ok = true; // 6 6 2 4  , 6 6 8 10
+				else if (s2 > e2 && (s1 > s2 || s1 < e2)) ok = true; // 6 6 4 2, 6 6 10 8
+			} else if (s1 < e1){
+				if (e1 < s2 && s2 <= e2) ok = true; // 6 8 10 10, 6 8 10 12 up
+				else if (s2 > e2 && e1 < e2) ok = true; // 6 8 12 10 up down, no overlap, 2nd is higher   
+				else if (s1 > s2 && s1 > e2) ok = true; // 6 8 4 4, 6 8 4 2, 6 8 2 4 up down, 2nd is lower
+			} else { // s1 > e1
+				if (e1 > s2 && s2 >= e2) ok = true;  // 8 6 4 4, 8 6 4 2 down
+				else if (e1 > s2 && e1 > e2) ok = true;  // 8 6 2 4 down up, no overlap,2nd is lower 
+				else if (s1 < s2 && s1 < e2) ok = true;  // 8 6 10 10, 8 6 10 12, 8 6 12 10, 1st down, no overlap,2nd is higher  
+			}
+		}
+		if (!ok){
+//			log.error("interval check not ok: ", s1,e1,s2,e2,"same side:",sameSide);
+		}
+		return ok;
+	}
+	
+	private ExtNumbers simulateRemovalOfHouseNumber(int hn, boolean left){
+		ExtNumbers help = new ExtNumbers(housenumberRoad);
+		help.prev = prev;
+		help.next = next;
+		
+		List<HousenumberMatch> modifiedHouses = new ArrayList<>(getHouses(left));
+		Iterator<HousenumberMatch> iter = modifiedHouses.iterator();
+		while (iter.hasNext()){
+			HousenumberMatch house = iter.next();
+			if (house.getHousenumber() == hn)
+				iter.remove();
+		}
+		help.setNumbers(modifiedHouses, startInRoad, endInRoad, left);
+		help.setNumbers(getHouses(!left), startInRoad, endInRoad, !left);
+		return help;
+	}
+
+	public boolean hasNumbers(){
+		return getNumbers().isEmpty() == false;
+	}
+	
+	/**
+	 * Try to add node(s) to decrease the distance of the calculated
+	 * position of an address.
+	 * @return 
+	 */
+	public ExtNumbers splitLargeGaps(){
+		if (hasNumbers() == false)
+			return this;
+		// calculate the length of each road segment and 
+		// the overall length covered by this interval
+		int numSegments = endInRoad - startInRoad;
+		double[] segmentLenghts = new double[numSegments];
+		double fullLength = 0;
+		for (int i = startInRoad; i < endInRoad; i++){
+			Coord c1 = getRoad().getPoints().get(i);
+			Coord c2 = getRoad().getPoints().get(i+1);
+			double len = c1.distance(c2);
+			segmentLenghts[i-startInRoad] = len;
+			fullLength += len;
+		}
+		if (fullLength < MAX_LOCATE_ERROR){
+			if (log.isDebugEnabled())
+				log.debug("segment",this.getNumbers(), "with length",formatLen(fullLength),"is considered OK");
+			
+			return this;
+		}
+		TreeMap<Integer, Double> searchPositions = new TreeMap<>();
+		boolean ok = calcSearchPositions(fullLength, searchPositions);
+		
+		if (!ok)
+			return this;
+		
+		double worstDelta = 0;
+		worstHouse = null;
+		for (int side = 0; side < 2; side++){
+			boolean left = side == 0;
+			List<HousenumberMatch> houses = getHouses(left);
+			for (HousenumberMatch house : houses){
+				double distToStart = 0;
+				for (int k = startInRoad; k < house.getSegment(); k++)
+					distToStart += segmentLenghts[k-startInRoad];
+				if (house.getSegmentFrac() > 0){
+					try {
+						distToStart += Math.min(1, house.getSegmentFrac()) * segmentLenghts[house.getSegment() - startInRoad];		
+					} catch (Exception e) {
+						log.error(e);
+					}
+				
+				}
+				Double searchDist = searchPositions.get(house.getHousenumber());
+				if (searchDist == null){
+					log.warn("can't compute address search result of",house);
+				} else {
+					double delta = distToStart - searchDist;
+					house.setSearchDist(delta);
+					if (Math.abs(delta) > worstDelta){
+						worstDelta = Math.abs(delta);
+						worstHouse = house;
+					}
+				}
+			}
+		}
+		if (worstDelta > MAX_LOCATE_ERROR){
+			if (log.isInfoEnabled())
+				log.info("trying to optimize address search for house number in road",getRoad(),worstHouse,"error before opt is",formatLen(worstDelta));
+			return tryChange(SR_OPT_LEN);
+		}
+		if (log.isDebugEnabled())
+			log.debug("segment",this.getNumbers(), "with length",formatLen(fullLength),"is OK, worst address search for house number in road",getRoad(),worstHouse,"error is",formatLen(worstDelta));
+		return this;
+	}
+
+	/**
+	 * Try to simulate a Garmin address search for each number covered by the interval. 
+	 * @param fullLength 
+	 * @param searchPositions filled by this routine
+	 * @return false if calculation failed
+	 */
+	private boolean calcSearchPositions(double fullLength, TreeMap<Integer, Double> searchPositions){
+		Numbers ivl = getNumbers();
+		for (int side = 0; side < 2; side++){
+			boolean left = side == 0;
+			NumberStyle style = ivl.getNumberStyle(left);
+			if (style != NumberStyle.NONE){
+				int start = ivl.getStart(left);
+				int end = ivl.getEnd(left);
+				int step = style == NumberStyle.BOTH ? 1 : 2;
+				if (step != 1 && start % 2 != end % 2){
+					log.error("internal error, bad interval in optimization",this);
+					return false;
+				}
+				if (start == end){
+					searchPositions.put(start, fullLength / 2);
+				} else {
+					int parts = Math.abs(end - start) / step;
+					double partLen = fullLength / parts;
+					if (start > end)
+						step = -step;
+					int hn = start;
+					double dist = 0;
+					while (true) {
+						searchPositions.put(hn, dist);
+						if (hn == end)
+							break;
+						dist += partLen;
+						hn += step;
+					} 
+					if (parts > 1)
+						assert Math.abs(fullLength - dist) < 0.1; 
+				}
+			}
+		}
+		return true;
+	}
+	
+	/**
+	 * Use Bresenham algorithm to get the Garmin points which are close to the line
+	 * described by c1 and c2 and the point p.
+	 * @param c1
+	 * @param c2
+	 * @param p
+	 * @return point with smallest perpendicular distance to line
+	 */
+	public static Coord rasterLineNearPoint(Coord c1, Coord c2, Coord p, boolean includeEndPoints){
+		int x0 = c1.getLongitude();
+		int y0 = c1.getLatitude();
+		int x1 = c2.getLongitude();
+		int y1 = c2.getLatitude();
+		Coord c1Dspl = c1.getDisplayedCoord();
+		Coord c2Dspl = c2.getDisplayedCoord();
+		int x = x0, y = y0;
+		int dx =  Math.abs(x1-x), sx = x<x1 ? 1 : -1;
+		int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
+		int err = dx+dy, e2; /* error value e_xy */
+		double minDistLine = Double.MAX_VALUE;
+		double minDistTarget = Double.MAX_VALUE;
+		int bestX = Integer.MAX_VALUE, bestY = Integer.MAX_VALUE;
+		
+		for(;;){  /* loop */
+			if (!includeEndPoints && x==x1 && y==y1)
+				break;
+			if (Math.abs(y - p.getLatitude()) <= 1  || Math.abs(x - p.getLongitude()) <= 1){
+				Coord t = new Coord(y, x);
+				double distToTarget = t.distance(p);
+
+				if (includeEndPoints || x != x0 || y != y0){
+					if (distToTarget < 10){ 
+						double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
+						if (distLine < minDistLine || distLine == minDistLine && distToTarget < minDistTarget || distLine < 0.2 && distToTarget < minDistTarget){
+							bestX = x;
+							bestY = y;
+							minDistLine = distLine;
+							minDistTarget = distToTarget;
+						} 
+					}
+				}
+			}
+			if (x==x1 && y==y1) break;
+			e2 = 2*err;
+			if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
+			if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
+		}
+		if (minDistLine == Double.MAX_VALUE)
+			return null;
+		Coord best = new Coord(bestY, bestX);
+		return best;
+	}
+
+
+	/**
+	 * Use Bresenham algorithm to get the Garmin points which are close to the line
+	 * described by c1 and c2 and the point p.
+	 * @param c1
+	 * @param c2
+	 * @param p
+	 * @param maxBefore tolerated distance before p
+	 * @param maxAfter tolerated distance after p
+	 * @return sorted map with closest points 
+	 */
+	public static TreeMap<Double,List<Coord>> rasterLineNearPoint2(Coord c1, Coord c2, Coord p, double maxBefore, double maxAfter){
+		int x0 = c1.getLongitude();
+		int y0 = c1.getLatitude();
+		int x1 = c2.getLongitude();
+		int y1 = c2.getLatitude();
+		Coord c1Dspl = c1.getDisplayedCoord();
+		Coord c2Dspl = c2.getDisplayedCoord();
+		int x = x0, y = y0;
+		int dx =  Math.abs(x1-x), sx = x<x1 ? 1 : -1;
+		int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
+		int err = dx+dy, e2; /* error value e_xy */
+		
+		TreeMap<Double,List<Coord>> sortedByDistToLine = new TreeMap<>();
+		boolean beforeTarget = true;
+		double lastDist = Double.NaN;
+		for(;;){  /* loop */
+			if (Math.abs(y - p.getLatitude()) <= 1  || Math.abs(x - p.getLongitude()) <= 1){
+				Coord t = new Coord(y, x);
+				double distToTarget = t.distance(p);
+
+				if (beforeTarget){
+					if (Double.isNaN(lastDist) == false && lastDist < distToTarget)
+						beforeTarget = false;
+				}
+				if (beforeTarget && distToTarget < maxBefore || !beforeTarget && distToTarget < maxAfter){ 
+					Double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
+					List<Coord> list = sortedByDistToLine.get(distLine);
+					if (list == null){
+						list = new ArrayList<>();
+						sortedByDistToLine.put(distLine, list);
+					}
+					list.add(t);
+				}
+				lastDist = distToTarget;
+			}
+			if (x==x1 && y==y1) 
+				break;
+			e2 = 2*err;
+			if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
+			if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
+		}
+		return sortedByDistToLine;
+	}
+
+	/**
+	 * Use Bresemham algorithm to get the Garmin points which are close to the line
+	 * described by c1 and c2 and the point p.
+	 * @param c1
+	 * @param c2
+	 * @param p
+	 * @return the list of points
+	 */
+	public static List<Coord> rasterLineNearPoint3(Coord c1, Coord c2, double maxDistToLine){
+		int x0 = c1.getLongitude();
+		int y0 = c1.getLatitude();
+		int x1 = c2.getLongitude();
+		int y1 = c2.getLatitude();
+		Coord c1Dspl = c1.getDisplayedCoord();
+		Coord c2Dspl = c2.getDisplayedCoord();
+		int x = x0, y = y0;
+		int dx =  Math.abs(x1-x), sx = x<x1 ? 1 : -1;
+		int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
+		int err = dx+dy, e2; /* error value e_xy */
+		
+		List<Coord> rendered = new ArrayList<>();
+		for(;;){  /* loop */
+			Coord t = new Coord(y, x);
+			double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
+			if (distLine <= maxDistToLine)
+				rendered.add(t);
+			if (x==x1 && y==y1) 
+				break;
+			e2 = 2*err;
+			if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
+			if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
+		}
+		return rendered;
+	}
+
+	private static int countOccurence(List<HousenumberMatch> houses, int num){
+		int count = 0;
+		for (HousenumberMatch house : houses){
+			if (house.getHousenumber() == num)
+				count++;
+		}
+		return count;
+	}
+	
+	/**
+	 * @param length
+	 * @return string with length, e.g. "0.23 m" or "116.12 m"
+	 */
+	private static String formatLen(double length){
+		return HousenumberGenerator.formatLen(length);
+	}
+
+	/** 
+	 * Try to detect the case that many house numbers have no specific order.
+	 * 
+	 */
+	public void detectRandom() {
+		int countFilledIvls = 0;
+		int countFilledSides = 0;
+		int countNotInOrder = 0;
+		for (ExtNumbers curr = this; curr != null; curr = curr.next){
+			if (curr.hasNumbers() == false)
+				continue;
+			countFilledIvls++;
+			if (curr.notInOrder(Numbers.LEFT))
+				countNotInOrder++;
+			if (curr.notInOrder(Numbers.RIGHT))
+				countNotInOrder++;
+			if (curr.getHouses(Numbers.LEFT).size() > 1)
+				++countFilledSides;
+			if (curr.getHouses(Numbers.RIGHT).size() > 1)
+				++countFilledSides;
+			
+		}
+		if (countNotInOrder > 0){
+			if (countNotInOrder > countFilledIvls || countNotInOrder > 2 || countFilledSides == countNotInOrder)
+				housenumberRoad.setRandom(true);
+		}
+	}
+	
+	private boolean isPlausible(){
+		if (getNumbers().isPlausible() == false)
+			return false;
+		if (multipleZipOrCity(true) || multipleZipOrCity(false))
+			return false;  
+
+		return true;
+	}
+
+	/**
+	 * Split this segment into two.
+	 * @param splitSegment
+	 */
+	private ExtNumbers split(int splitSegment){
+		getRoad().getPoints().get(splitSegment).setNumberNode(true);
+		
+		ExtNumbers first = divide();
+		// distribute the houses. The caller has to make sure that
+		// they are assigned to the wanted segment and sorted. 
+		for (int side = 0; side < 2; side++){
+			boolean left = side == 0;
+			List<HousenumberMatch> houses = getHouses(left);
+			if (houses.isEmpty()) 
+				continue;
+			int used = first.setNumbers(houses, startInRoad, splitSegment, left);
+			first.next.setNumbers(houses.subList(used, houses.size()), splitSegment, endInRoad, left);
+		}
+
+		return first;
+	}
+
+	public String toString(){
+		return getRoad().toString() + getHouses(Numbers.LEFT).toString() + getHouses(Numbers.RIGHT).toString();
+	}
+
+}
+
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberElem.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberElem.java
new file mode 100644
index 0000000..fdbd5b5
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberElem.java
@@ -0,0 +1,110 @@
+package uk.me.parabola.mkgmap.osmstyle.housenumber;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.general.CityInfo;
+import uk.me.parabola.mkgmap.general.MapRoad;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.Node;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+import uk.me.parabola.util.Locatable;
+
+class HousenumberElem implements Locatable{
+	protected final Element element;
+	private int housenumber;
+	private String sign;
+	private String place;
+	private CityInfo cityInfo;
+	private ZipCodeInfo zipCode;
+	private String street;
+	//cache for Way elements to prevent calling Way.getCofG() repeatedly
+	private Coord location;
+	
+	public HousenumberElem(Element el, CityInfo ci) {
+		this.element = el;
+		this.cityInfo = ci;
+	}
+
+	public HousenumberElem(HousenumberElem he) {
+		this.element = he.element;
+		this.housenumber = he.housenumber;
+		this.sign = he.sign;
+		this.street = he.street;
+		this.place = he.place;
+		this.cityInfo = he.cityInfo;
+		this.zipCode = he.zipCode;
+		this.location = he.location;
+	}
+
+	public Element getElement() {
+		return element;
+	}
+
+	public int getHousenumber() {
+		return housenumber;
+	}
+
+	public void setHousenumber(int housenumber) {
+		this.housenumber = housenumber;
+	}
+
+	public String getSign() {
+		return sign;
+	}
+
+	public void setSign(String sign) {
+		this.sign = sign;
+	}
+
+	public String getPlace() {
+		return place;
+	}
+
+	public void setPlace(String place) {
+		this.place = place;
+	}
+
+	public String getStreet() {
+		return street;
+	}
+
+	public void setStreet(String street) {
+		this.street = street;
+	}
+
+	public MapRoad getRoad(){
+		return null;
+	}
+	
+	public CityInfo getCityInfo(){
+		return cityInfo;
+	}
+	
+	public void setZipCode(ZipCodeInfo zip){
+		zipCode = zip;
+	}
+	
+	public ZipCodeInfo getZipCode(){
+		return zipCode;
+	}
+	
+	@Override
+	public Coord getLocation() {
+		if (location == null){
+			if (element instanceof Node)
+				location = ((Node) element).getLocation();
+			else 
+				location = ((Way) element).getCofG();
+		}
+		return location;
+	}
+	
+	@Override
+	public String toString() {
+		if (street != null)
+			return street + " " + sign;
+		if (place != null)
+			return place + " " + sign;
+		return "?" + " " + sign;
+	}
+}
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
index b9d8950..c6ccfaf 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
@@ -13,53 +13,99 @@
 
 package uk.me.parabola.mkgmap.osmstyle.housenumber;
 
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import uk.me.parabola.imgfmt.MapFailedException;
+import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.imgfmt.app.CoordNode;
-import uk.me.parabola.imgfmt.app.net.NumberStyle;
 import uk.me.parabola.imgfmt.app.net.Numbers;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.CityInfo;
 import uk.me.parabola.mkgmap.general.LineAdder;
 import uk.me.parabola.mkgmap.general.MapRoad;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
 import uk.me.parabola.mkgmap.reader.osm.Element;
-import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
+import uk.me.parabola.mkgmap.reader.osm.HousenumberHooks;
 import uk.me.parabola.mkgmap.reader.osm.Node;
+import uk.me.parabola.mkgmap.reader.osm.POIGeneratorHook;
 import uk.me.parabola.mkgmap.reader.osm.Relation;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.Way;
+import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.KdTree;
+import uk.me.parabola.util.Locatable;
 import uk.me.parabola.util.MultiHashMap;
 
 /**
  * Collects all data required for OSM house number handling and adds the
  * house number information to the roads.
  * 
- * @author WanMil
+ * @author WanMil, Gerd Petermann
  */
 public class HousenumberGenerator {
-
-	private static final Logger log = Logger
-			.getLogger(HousenumberGenerator.class);
+	private static final Logger log = Logger.getLogger(HousenumberGenerator.class);
 
 	/** Gives the maximum distance between house number element and the matching road */
-	private static final double MAX_DISTANCE_TO_ROAD = 150d;
+	public static final double MAX_DISTANCE_TO_ROAD = 150d;
 	
 	private boolean numbersEnabled;
+
+	// options for handling of unnamed (service?) roads	
+	private int nameSearchDepth = 3;
+
+	private MultiHashMap<String, HousenumberIvl> interpolationWays;
+	private List<MapRoad> allRoads;
+	private Map<Long,Integer> interpolationNodes;
+	private List<HousenumberElem> houseElems;
+	private HashMap<CityInfo, CityInfo> cityInfos = new HashMap<>();
+	private HashMap<ZipCodeInfo, ZipCodeInfo> zipInfos = new HashMap<>();
+
+	private static final short housenumberTagKey1 =  TagDict.getInstance().xlate("mkgmap:housenumber");
+	private static final short housenumberTagKey2 =  TagDict.getInstance().xlate("addr:housenumber");
+	private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street");
+	private static final short addrStreetTagKey = TagDict.getInstance().xlate("addr:street");
+	private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation");
+	private static final short addrPlaceTagKey = TagDict.getInstance().xlate("addr:place");
+	private static final short cityTagKey = TagDict.getInstance().xlate("mkgmap:city");
+	private static final short regionTagKey = TagDict.getInstance().xlate("mkgmap:region");
+	private static final short countryTagKey = TagDict.getInstance().xlate("mkgmap:country");
+	private static final short postalCodeTagKey = TagDict.getInstance().xlate("mkgmap:postal_code");
+	private static final short numbersTagKey = TagDict.getInstance().xlate("mkgmap:numbers");
 	
-	private MultiHashMap<String, MapRoad> roadByNames;
-	private List<MapRoad> roads;
-	private MultiHashMap<String, Element> houseNumbers;
-	
-	public HousenumberGenerator(Properties props) {
-		this.roadByNames = new MultiHashMap<String,MapRoad>();
-		this.houseNumbers = new MultiHashMap<String,Element>();
-		this.roads = new ArrayList<MapRoad>();
+	public HousenumberGenerator(EnhancedProperties props) {
+		this.interpolationWays = new MultiHashMap<>();
+		this.allRoads = new ArrayList<>();
+		this.interpolationNodes = new HashMap<>();
+		this.houseElems = new ArrayList<>();
 		
-		numbersEnabled=props.containsKey("housenumbers");
+		numbersEnabled = props.containsKey("housenumbers");
+		int n = props.getProperty("name-service-roads", 3);
+		if (n != nameSearchDepth){
+			nameSearchDepth = Math.min(25, Math.max(0, n));
+			if (nameSearchDepth != n)
+				System.err.println("name-service-roads=" + n + " was changed to name-service-roads=" + nameSearchDepth);
+		}
 	}
 
 	/**
@@ -68,14 +114,155 @@ public class HousenumberGenerator {
 	 * @return the street name (or {@code null} if no street name set)
 	 */
 	private static String getStreetname(Element e) {
-		String streetname = e.getTag("mkgmap:street");
+		String streetname = e.getTag(streetTagKey);
 		if (streetname == null) {
-			streetname = e.getTag("addr:street");
+			streetname = e.getTag(addrStreetTagKey);
 		}	
 		return streetname;
 	}
 	
 	/**
+	 * Retrieves the house number of this element.
+	 * @param e an OSM element
+	 * @return the house number (or {@code null} if no house number set)
+	 */
+	public static String getHousenumber(Element e) {
+		String res = e.getTag(housenumberTagKey1); 
+		if (res != null)
+			return res;
+		return e.getTag(housenumberTagKey2);
+	}
+	
+	/**
+	 * Parses the house number string. It accepts the first positive number part
+	 * of a string. So all leading and preceding non number parts are ignored.
+	 * So the following strings are accepted:
+	 * <table>
+	 * <tr>
+	 * <th>Input</th>
+	 * <th>Output</th>
+	 * </tr>
+	 * <tr>
+	 * <td>23</td>
+	 * <td>23</td>
+	 * </tr>
+	 * <tr>
+	 * <td>-23</td>
+	 * <td>23</td>
+	 * </tr>
+	 * <tr>
+	 * <td>21-23</td>
+	 * <td>21</td>
+	 * </tr>
+	 * <tr>
+	 * <td>Abc 21</td>
+	 * <td>21</td>
+	 * </tr>
+	 * <tr>
+	 * <td>Abc 21.45</td>
+	 * <td>21</td>
+	 * </tr>
+	 * <tr>
+	 * <td>21 Main Street</td>
+	 * <td>21</td>
+	 * </tr>
+	 * <tr>
+	 * <td>Main Street</td>
+	 * <td><i>IllegalArgumentException</i></td>
+	 * </tr>
+	 * </table>
+	 * @throws IllegalArgumentException if parsing fails
+	 */
+	private static Integer parseHousenumber(String housenumberString) {
+		if (housenumberString == null) {
+			return null;
+		}
+		
+		// the housenumber must match against the pattern <anything>number<notnumber><anything>
+		int housenumber;
+		Pattern p = Pattern.compile("\\D*(\\d+)\\D?.*");
+		Matcher m = p.matcher(housenumberString);
+		if (m.matches() == false) {
+			return null;
+		}
+		try {
+			// get the number part and parse it
+			housenumber = Integer.parseInt(m.group(1));
+		} catch (NumberFormatException exp) {
+			return null;
+		}
+		return housenumber;
+	}
+
+	
+	private HousenumberElem parseElement(Element el, String sign){
+		String city = el.getTag(cityTagKey);
+		String region = el.getTag(regionTagKey);
+		String country = el.getTag(countryTagKey);
+		CityInfo ci = getCityInfos(city,region,country);
+		HousenumberElem house = new HousenumberElem(el, ci);
+		if (house.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: " + el.toBrowseURL() + " " + el);
+			log.error("Please report on the mkgmap mailing list.");
+			log.error("Continue creating the map. This should be possible without a problem.");
+			return null;
+		}
+		
+		house.setSign(sign);
+		Integer hn = parseHousenumber(sign);
+		if (hn == null){
+			if (log.isDebugEnabled())
+				log.debug("No housenumber (", el.toBrowseURL(), "): ", sign);
+			return null;
+		}
+		if (hn < 0 || hn > 1_000_000){
+			log.warn("Number looks wrong, is ignored",house.getSign(),hn,"element",el.toBrowseURL());
+			return null;
+		}
+		house.setHousenumber(hn);
+		house.setStreet(getStreetname(el));
+		house.setPlace(el.getTag(addrPlaceTagKey));
+		String zipStr = el.getTag(postalCodeTagKey);
+		ZipCodeInfo zip = getZipInfos(zipStr);
+		house.setZipCode(zip);
+		return house;
+	}
+	
+	private CityInfo getCityInfos(String city, String region, String country) {
+		CityInfo ci = new CityInfo(city, region, country);
+		CityInfo ciOld = cityInfos.get(ci);
+		if (ciOld != null)
+			return ciOld;
+//		log.debug(ci);
+		cityInfos.put(ci, ci);
+		return ci;
+	}
+
+	private ZipCodeInfo getZipInfos(String zipStr) {
+		ZipCodeInfo zip = new ZipCodeInfo(zipStr);
+		ZipCodeInfo zipOld = zipInfos.get(zip);
+		if (zipOld != null)
+			return zipOld;
+		zipInfos.put(zip, zip);
+		return zip;
+	}
+
+	private HousenumberElem handleElement(Element el){
+		String sign = getHousenumber(el);
+		if (sign == null)
+			return null;
+		
+		HousenumberElem he = parseElement(el, sign);
+		if (he == null)
+			return null;
+		houseElems.add(he);
+		return he;
+	}
+	/**
 	 * Adds a node for house number processing.
 	 * @param n an OSM node
 	 */
@@ -83,15 +270,18 @@ public class HousenumberGenerator {
 		if (numbersEnabled == false) {
 			return;
 		}
-		if (HousenumberMatch.getHousenumber(n) != null) {
-			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.");
-			}
+		if("false".equals(n.getTag(numbersTagKey)))
+			return;
+		
+		if ("true".equals(n.getTag(POIGeneratorHook.AREA2POI_TAG))){
+			// ignore POI created for buildings
+			return; 		
 		}
+		HousenumberElem houseElem = handleElement(n);
+		if (houseElem == null)
+			return;
+		if (n.getTag(HousenumberHooks.partOfInterpolationTagKey) != null)
+			interpolationNodes.put(n.getId(),houseElems.size()-1);
 	}
 	
 	/**
@@ -102,35 +292,186 @@ public class HousenumberGenerator {
 		if (numbersEnabled == false) {
 			return;
 		}
-		if (HousenumberMatch.getHousenumber(w) != null) {
-			String streetname = getStreetname(w);
-			if (streetname != null) {
-				houseNumbers.add(streetname, w);
+		if("false".equals(w.getTag(numbersTagKey)))
+			return;
+		
+		String ai = w.getTag(addrInterpolationTagKey);
+		if (ai != null){
+			// the way has the addr:interpolation=* tag, parse info
+			// created by the HousenumberHook
+			List<HousenumberElem> nodes = new ArrayList<>();
+			String nodeIds = w.getTag(HousenumberHooks.mkgmapNodeIdsTagKey);
+			if (nodeIds == null){
+				// way was rejected by hook
 			} 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.");
+				String[] ids = nodeIds.split(",");
+				for (String idString : ids){
+					Long id = Long.decode(idString);
+					Integer elemPos = interpolationNodes.get(id);
+					if (elemPos != null){
+						HousenumberElem node = houseElems.get(elemPos);
+						if (node != null){
+							assert node.getElement().getId() == id;
+							nodes.add(node);
+						}
+					}
 				}
+				interpretInterpolationWay(w, nodes);
 			}
+			return;
 		}
+		
+		if (w.hasIdenticalEndPoints()){
+			// we are only interested in polygons now
+			handleElement(w);
+		}
+
 	}
 	
-	
+	/**
+	 * Use the information provided by the addr:interpolation tag
+	 * to generate additional house number elements. This increases
+	 * the likelihood that a road segment is associated with the right 
+	 * number ranges. 
+	 * @param w the way
+	 * @param nodes2 
+	 * @param nodes list of nodes
+	 */
+	private void interpretInterpolationWay(Way w, List<HousenumberElem> nodes) {
+		int numNodes = nodes.size();
+		String addrInterpolationMethod = w.getTag(addrInterpolationTagKey);
+		int step = 0;
+		switch (addrInterpolationMethod) {
+		case "all":
+		case "1":
+			step = 1;
+			break;
+		case "even":
+		case "odd":
+		case "2":
+			step = 2;
+			break;
+		default:
+			break;
+		}
+		if (step == 0)
+			return; // should not happen here
+		int pos = 0;
+		List<HousenumberIvl> hivls = new ArrayList<>();
+		String streetName = null;
+		for (int i = 0; i+1 < numNodes; i++){
+			// the way have other points, find the sequence including the pair of nodes    
+			HousenumberElem he1 = nodes.get(i);
+			HousenumberElem he2 = nodes.get(i+1);
+			int pos1 = -1, pos2 = -1;
+			for (int k = pos; k < w.getPoints().size(); k++){
+				if (w.getPoints().get(k) == he1.getLocation()){
+					pos1 = k;
+					break;
+				}
+			}
+			if (pos1 < 0){
+				log.error("addr:interpolation node not found in way",w);
+				return;
+			}
+			for (int k = pos1+1; k < w.getPoints().size(); k++){
+				if (w.getPoints().get(k) == he2.getLocation()){
+					pos2 = k;
+					break;
+				}
+			}
+			if (pos2 < 0){
+				log.error("addr:interpolation node not found in way",w);
+				return;
+			}
+			pos = pos2;
+			String street = he1.getStreet();
+			if (street != null && street.equals(he2.getStreet())){
+				if (streetName == null)
+					streetName = street;
+				else if (streetName.equals(street) == false){
+					log.warn(w.toBrowseURL(),"addr:interpolation=even is used with different street names",streetName,street);
+					return;
+				}
+				
+				int start = he1.getHousenumber();
+				int end = he2.getHousenumber();
+				int steps;
+				if (start < end){
+					steps = (end - start) / step - 1;
+				} else {
+					steps = (start - end) / step - 1;
+				}
+				HousenumberIvl hivl = new HousenumberIvl(street, w, (Node)he1.element, (Node)he2.element);
+				hivl.setStart(start);
+				hivl.setEnd(end);
+				hivl.setStep(step);
+				hivl.setSteps(steps);
+				hivl.setPoints(w.getPoints().subList(pos1, pos2+1));
+//				if (pos1 > 0){
+//					double angle = Utils.getAngle(w.getPoints().get(pos1-1), w.getPoints().get(pos1), w.getPoints().get(pos1+1));
+//					if (Math.abs(angle) > 75){
+//						log.warn(w.toBrowseURL(),"addr:interpolation way has sharp angle at number",start,"cannot use it");
+//						return;
+//					}
+//						
+//				}
+				
+				hivls.add(hivl);
+				if ("even".equals(addrInterpolationMethod) && (start % 2 != 0 || end % 2 != 0)){
+					log.warn(w.toBrowseURL(),"addr:interpolation=even is used with odd housenumber(s)",start,end);
+					return;
+				}
+				if ("odd".equals(addrInterpolationMethod) && (start % 2 == 0 || end % 2 == 0)){
+					log.warn(w.toBrowseURL(),"addr:interpolation=odd is used with even housenumber(s)",start,end);
+					return;
+				}
+				
+				if (start == end && he1.getSign().equals(he2.getSign())){
+					// handle special case from CanVec imports  
+					if (pos1 == 0 && pos2 +1 == w.getPoints().size()){
+						hivl.setEqualEnds();
+						log.warn(w.toBrowseURL(),"addr:interpolation way connects two points with equal numbers, numbers are ignored");
+					}
+				}
+			}
+		}
+		for (HousenumberIvl  hivl : hivls)
+			interpolationWays.add(streetName, hivl);
+	}
+
+	private MapRoad firstRoadSameOSMWay = null;
 	/**
 	 * Adds a road to be processed by the house number generator.
 	 * @param osmRoad the OSM way the defines the road 
 	 * @param road a road
 	 */
 	public void addRoad(Way osmRoad, MapRoad road) {
-		roads.add(road);
+		allRoads.add(road);
 		if (numbersEnabled) {
-			String name = getStreetname(osmRoad); 
-			if (name != null) {
-				if (log.isDebugEnabled())
-					log.debug("Housenumber - Streetname:", name, "Way:",osmRoad.getId(),osmRoad.toTagString());
-				roadByNames.add(name, road);
+			if("false".equals(osmRoad.getTag(numbersTagKey))) 
+				road.setSkipHousenumberProcessing(true);
+			
+			/*
+			 * If the style adds the same OSM way as two or more routable ways, we use
+			 * only the first. This ensures that we don't try to assign numbers from bad
+			 * matches to these copies.
+			 */
+			if(!road.isSkipHousenumberProcessing()){
+				if (firstRoadSameOSMWay != null){
+					if (firstRoadSameOSMWay.getRoadDef().getId() == road.getRoadDef().getId()){
+						if (firstRoadSameOSMWay.getPoints().equals(road.getPoints())){
+							road.setSkipHousenumberProcessing(true);
+							return;
+						}
+					}
+				} 
+				firstRoadSameOSMWay = road;
+				String name = road.getStreet(); 
+				if (name != null) {
+					if (log.isDebugEnabled())
+						log.debug("Housenumber - Streetname:", name, "Way:",osmRoad.getId(),osmRoad.toTagString());
+				}
 			}
 		} 
 	}
@@ -174,23 +515,25 @@ public class HousenumberGenerator {
 							log.warn("Relation",r.toBrowseURL(),": role of member",w.toBrowseURL(),"unclear");
 						break;
 					default:
+						if ("associatedStreet".equals(relType)) 
+							log.warn("Relation",r.toBrowseURL(),": don't know how to handle member with role",role);
 						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;
+			List<Element> unnamedStreetElems = new ArrayList<>();
 			boolean nameFromStreetsIsUnclear = false;
 			if (streets.isEmpty() == false) {
 				for (Element street : streets) {
-					String roadName = street.getTag("name");
-					if (roadName == null)
+					String roadName = street.getTag(streetTagKey);
+					if (roadName == null) 
+						roadName = street.getTag("name");
+					if (roadName == null){
+						unnamedStreetElems.add(street);
 						continue;
+					}
 					if (streetNameFromRoads == null)
 						streetNameFromRoads = roadName;
 					else if (streetNameFromRoads.equals(roadName) == false)
@@ -208,6 +551,10 @@ public class HousenumberGenerator {
 			} else {
 				if (streetNameFromRoads != null){
 					if (nameFromStreetsIsUnclear == false && streetName.equals(streetNameFromRoads) == false){
+						if (unnamedStreetElems.isEmpty() == false){
+							log.warn("Relation",r.toBrowseURL(),": ignored, street name is not clear.");
+							return;
+						}
 						log.warn("Relation",r.toBrowseURL(),": street name is not clear, using the name from the way, not that of the relation.");
 						streetName = streetNameFromRoads;
 					} 
@@ -216,17 +563,27 @@ public class HousenumberGenerator {
 					}
 				} 
 			}
-			int countOK = 0;
+			int countModHouses = 0;
 			if (streetName != null && streetName.isEmpty() == false){
 				for (Element house : houses) {
 					if (addStreetTagFromRel(r, house, streetName) )
-						countOK++;
+						countModHouses++;
+				}
+				for (Element street : unnamedStreetElems) {
+					street.addTag(streetTagKey, streetName);
 				}
 			}
-			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");
+			if (log.isInfoEnabled()){
+				if (countModHouses > 0 || !unnamedStreetElems.isEmpty()){
+					if (countModHouses > 0)
+						log.info("Relation",r.toBrowseURL(),": added tag mkgmap:street=",streetName,"to",countModHouses,"of",houses.size(),"house members" );
+					if (!unnamedStreetElems.isEmpty())
+						log.info("Relation",r.toBrowseURL(),": added tag mkgmap:street=",streetName,"to",unnamedStreetElems.size(),"of",streets.size(),"street members" );
+				}
+				else 
+					log.info("Relation",r.toBrowseURL(),": ignored, no house or street member was changed");
+			}
+
 		}
 	}
 	
@@ -234,18 +591,18 @@ public class HousenumberGenerator {
 	 * 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){
+	private static boolean addStreetTagFromRel(Relation r, Element house, String streetName){
 		String addrStreet = getStreetname(house);
 		if (addrStreet == null){
-			house.addTag("mkgmap:street", streetName);
+			house.addTag(streetTagKey, 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){
+			if (house.getTag(streetTagKey) != 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");
+				house.deleteTag(streetTagKey);
 			}
 			else 
 				log.warn("Relation",r.toBrowseURL(),": street name from relation doesn't match existing name for house",house.toBrowseURL());
@@ -254,253 +611,1190 @@ public class HousenumberGenerator {
 	}
 	
 	
-	public void generate(LineAdder adder) {
+	/**
+	 * 
+	 * @param adder
+	 * @param naxNodeId the highest nodeId used before
+	 */
+	public void generate(LineAdder adder, int naxNodeId) {
 		if (numbersEnabled) {
-			for (Entry<String, List<Element>> numbers : houseNumbers.entrySet()) {
-				List<MapRoad> possibleRoads = roadByNames.get(numbers.getKey());
-
-				if (possibleRoads.isEmpty()) {
-					continue;
+			MultiHashMap<MapRoad,HousenumberMatch> initialHousesForRoads = findClosestRoadsToHouse();
+			identifyServiceRoads();
+			
+			handleInterpolationWays(initialHousesForRoads);
+			
+			List<HousenumberRoad> hnrList = createHousenumberRoads(initialHousesForRoads);
+			initialHousesForRoads = null;
+			log.info("found",hnrList.size(),"road candidates for address search");
+			
+			useAddrPlaceTag(hnrList);
+			Map<MapRoad, HousenumberRoad> road2HousenumberRoadMap = new HashMap<>();
+			for (HousenumberRoad hnr : hnrList){
+				road2HousenumberRoadMap.put(hnr.getRoad(), hnr);
+			}
+			Int2ObjectOpenHashMap<HashSet<MapRoad>> nodeId2RoadLists = new Int2ObjectOpenHashMap<>();
+			for (MapRoad road : allRoads){
+				for (Coord co : road.getPoints()){
+					if (co.getId() == 0)
+						continue;
+					HashSet<MapRoad> connectedRoads = nodeId2RoadLists.get(co.getId());
+					if (connectedRoads == null){
+						connectedRoads = new HashSet<>();
+						nodeId2RoadLists.put(co.getId(), connectedRoads);
+					}
+					connectedRoads.add(road);
+				}
+			}
+			List<HousenumberRoad> addedRoads = new ArrayList<>();
+			Iterator<HousenumberRoad> iter = hnrList.iterator();
+			while (iter.hasNext()){
+				HousenumberRoad hnr = iter.next();
+				
+				List<HousenumberMatch> lostHouses = hnr.checkStreetName(road2HousenumberRoadMap, nodeId2RoadLists);
+				for (HousenumberMatch house : lostHouses){
+					MapRoad r = house.getRoad();
+					if (r != null){
+						HousenumberRoad hnr2 = road2HousenumberRoadMap.get(r);
+						if (hnr2 == null){
+							CityInfo ci = getCityInfos(r.getCity(), r.getRegion(), r.getCountry());
+							hnr2 = new HousenumberRoad(r, ci, Arrays.asList(house));
+							if (r.getZip() != null)
+								hnr2.setZipCodeInfo(getZipInfos(r.getZip()));
+							road2HousenumberRoadMap.put(r,hnr2);
+							addedRoads.add(hnr2);
+						} else {
+							hnr2.addHouse(house);
+						}
+					}
+				}
+				if (hnr.getName() == null){
+					iter.remove();
+					for (HousenumberMatch house : hnr.getHouses()){
+						log.warn("found no plausible road name for address",house.getElement().toBrowseURL(),", closest road id:",house.getRoad());
+					}
+				}
+			}
+			hnrList.addAll(addedRoads);
+			// TODO: interpolate addr:interpolation houses
+			removeDupsGroupedByCityAndName(hnrList);
+			
+			// group by street name and city
+			TreeMap<String, TreeMap<CityInfo, List<HousenumberRoad>>> streetnameCityRoadMap = new TreeMap<>();
+			for (HousenumberRoad hnr : hnrList){
+				TreeMap<CityInfo, List<HousenumberRoad>> cluster = streetnameCityRoadMap.get(hnr.getName());
+				if (cluster == null){
+					cluster = new TreeMap<>();
+					streetnameCityRoadMap.put(hnr.getName(), cluster);
+				}
+				List<HousenumberRoad> roadsInCluster = cluster.get(hnr.getRoadCityInfo());
+				if (roadsInCluster == null){
+					roadsInCluster = new ArrayList<>();
+					cluster.put(hnr.getRoadCityInfo(), roadsInCluster);
+				}
+				roadsInCluster.add(hnr);
+			}
+			
+			for (Entry<String, TreeMap<CityInfo, List<HousenumberRoad>>> streetNameEntry : streetnameCityRoadMap.entrySet()){
+				String streetName = streetNameEntry.getKey();
+				List<HousenumberRoad> roadsWithStreetName = new ArrayList<>();
+				for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()){
+					roadsWithStreetName.addAll(clusterEntry.getValue());
+				}
+				useInterpolationInfo(streetName, roadsWithStreetName, road2HousenumberRoadMap);
+				
+				for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()){
+					List<HousenumberRoad> roadsInCluster = clusterEntry.getValue();
+					if (log.isDebugEnabled()){
+						log.debug("processing road(s) with name",streetName,"in",clusterEntry.getKey() );
+					}
+					for (HousenumberRoad hnr : roadsInCluster){
+						hnr.buildIntervals();
+					}
+					boolean optimized = false;
+					for (int loop = 0; loop < 10; loop++){
+						for (HousenumberRoad hnr : roadsInCluster){
+							hnr.checkIntervals();
+						}
+						checkWrongRoadAssignmments(roadsInCluster);
+						boolean changed = hasChanges(roadsInCluster);
+						if (!optimized && !changed){
+							for (HousenumberRoad hnr : roadsInCluster){
+								hnr.improveSearchResults();
+							}
+							changed = hasChanges(roadsInCluster);
+							optimized = true;
+						}
+						if (!changed)
+							break;
+					}
+					for (HousenumberRoad hnr : roadsInCluster){
+						hnr.setNumbers();
+					}
 				}
-
-				match(numbers.getKey(), numbers.getValue(), possibleRoads);
 			}
 		}
-		
-		for (MapRoad r : roads) {
+			
+		if (log.isInfoEnabled()){
+			for (HousenumberElem house : houseElems){
+				if (house.getRoad() == null){
+					if (house.getStreet() != null)
+						log.info("found no plausible road for house number element",house.getElement().toBrowseURL(),house.getStreet(),house.getSign());
+					else 
+						log.info("found no plausible road for house number element",house.getElement().toBrowseURL());
+				}
+			}
+		}
+		for (MapRoad r : allRoads) {
+			if (log.isDebugEnabled()){
+				List<Numbers> finalNumbers = r.getRoadDef().getNumbersList();
+				if (finalNumbers != null){
+					log.info("id:"+r.getRoadDef().getId(),", final numbers,",r,"in",r.getCity());
+					for (Numbers cn : finalNumbers){
+						if (cn.isEmpty())
+							continue;
+						log.info("id:"+r.getRoadDef().getId(),", Left: ",cn.getLeftNumberStyle(),cn.getIndex(),"Start:",cn.getLeftStart(),"End:",cn.getLeftEnd());
+						log.info("id:"+r.getRoadDef().getId(),", Right:",cn.getRightNumberStyle(),cn.getIndex(),"Start:",cn.getRightStart(),"End:",cn.getRightEnd());
+					}
+				}
+			}
 			adder.add(r);
 		}
-		
-		houseNumbers.clear();
-		roadByNames.clear();
-		roads.clear();
 	}
 	
+	private List<HousenumberRoad> createHousenumberRoads(
+			MultiHashMap<MapRoad, HousenumberMatch> initialHousesForRoads) {
+		List<HousenumberRoad> hnrList = new ArrayList<>();
+		for (MapRoad road : allRoads){
+			if (road.isSkipHousenumberProcessing())
+				continue;
+			List<HousenumberMatch> houses = initialHousesForRoads.get(road);
+			if (houses == null || houses.isEmpty())
+				continue;
+			CityInfo ci = getCityInfos(road.getCity(), road.getRegion(), road.getCountry());
+			HousenumberRoad hnr = new HousenumberRoad(road, ci, houses);
+			if (road.getZip() != null)
+				hnr.setZipCodeInfo(getZipInfos(road.getZip()));
+				
+			hnrList.add(hnr);
+		}
+		return hnrList;
+	}
+
+	private MultiHashMap<MapRoad, HousenumberMatch> findClosestRoadsToHouse() {
+		// build road index
+		long t1 = System.currentTimeMillis();
+		RoadSegmentIndex roadSegmentIndex = new RoadSegmentIndex(allRoads, MAX_DISTANCE_TO_ROAD);
+		long t2 = System.currentTimeMillis();
+		log.debug("creation of road index took",t2-t1,"ms");
+		
+		long t3 = System.currentTimeMillis();
+		MultiHashMap<MapRoad,HousenumberMatch> initialHousesForRoads = new MultiHashMap<>();
+		for (int i = 0; i < houseElems.size(); i++){
+			HousenumberElem house = houseElems.get(i);
+			HousenumberMatch bestMatch = roadSegmentIndex.createHousenumberMatch(house);
+			houseElems.set(i, bestMatch);
+			if (bestMatch.getRoad() == null){
+				bestMatch.setIgnored(true); // XXX maybe create a pseudo road with zero length?
+//				log.warn("found no plausible road for house number element",house.getElement().toBrowseURL());
+				continue;
+			}
+			initialHousesForRoads.add(bestMatch.getRoad(), bestMatch);
+		}
+		long t4 = System.currentTimeMillis();
+		log.debug("identification of closest road for each house took",t4-t3,"ms");
+		
+		return initialHousesForRoads;
+	}
+
+	private void useAddrPlaceTag(List<HousenumberRoad> hnrList){
+		HashMap<CityInfo,MultiHashMap<String,HousenumberMatch>> cityPlaceHouseMap = new LinkedHashMap<>();
+		for (int i = 0; i < houseElems.size(); i++){
+			HousenumberElem house = houseElems.get(i);
+			if (house.getRoad() == null)
+				continue;
+			if (house.getPlace() == null)
+				continue;
+			MultiHashMap<String, HousenumberMatch> subMap = cityPlaceHouseMap.get(house.getCityInfo());
+			if (subMap == null){
+				subMap = new MultiHashMap<>();
+				cityPlaceHouseMap.put(house.getCityInfo(), subMap);
+			}
+			subMap.add(house.getPlace(), (HousenumberMatch) house);
+		}
+		log.info("analysing",cityPlaceHouseMap.size(),"cities with addr:place=* houses" );
+		for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityPlaceHouseMap.entrySet()){
+			CityInfo cityInfo = topEntry.getKey();
+			List<String> placeNames = new ArrayList<>(topEntry.getValue().keySet());
+			Collections.sort(placeNames);
+			for (String placeName : placeNames){
+				List<HousenumberMatch> placeHouses = topEntry.getValue().get(placeName);
+				HashSet<HousenumberRoad> roads = new LinkedHashSet<>();
+				Int2IntOpenHashMap usedNumbers = new Int2IntOpenHashMap();
+				HashMap<String,Integer> usedSigns = new HashMap<>();
+				int dupSigns = 0;
+				int dupNumbers = 0;
+				int housesWithStreet = 0;
+				int housesWithMatchingStreet = 0;
+				int roadsWithNames = 0;
+				int unnamedCloseRoads = 0;
+				
+				for (HousenumberMatch house : placeHouses){
+					if (house.getStreet() != null ){
+						++housesWithStreet;
+						if (house.getStreet().equals(house.getRoad().getStreet())){
+							++housesWithMatchingStreet;
+						}
+						
+					} else {
+						if (house.getRoad().getStreet() == null)
+							++unnamedCloseRoads;
+					}
+					boolean added = roads.add(house.getHousenumberRoad());
+					if (added && house.getRoad().getStreet() != null)
+						++roadsWithNames;
+					int oldCount = usedNumbers.put(house.getHousenumber(),1);
+					if (oldCount != 0){
+						usedNumbers.put(house.getHousenumber(), oldCount + 1);
+						++dupNumbers;
+					}
+					Integer oldSignCount = usedSigns.put(house.getSign(), 1);
+					if (oldSignCount != null){
+						usedSigns.put(house.getSign(), oldSignCount + 1);
+						++dupSigns;
+					}
+				}
+				
+				if (log.isDebugEnabled()){
+					log.debug("place",placeName,"in city",cityInfo, ":", "houses:", placeHouses.size(),
+							",duplicate numbers/signs:", dupNumbers+"/"+dupSigns,
+							",roads (named/unnamed):", roads.size(),"("+roadsWithNames+"/"+(roads.size()- roadsWithNames)+")",
+							",houses without addr:street:", placeHouses.size() - housesWithStreet,
+							",street = name of closest road:", housesWithMatchingStreet,
+							",houses without addr:street near named road:",	unnamedCloseRoads);
+				}
+				if ((float) dupSigns / placeHouses.size() < 0.25 ){
+					if (log.isDebugEnabled())
+						log.debug("will not use gaps in intervals for roads in",placeName );
+					for (HousenumberRoad hnr : roads){
+						hnr.setRemoveGaps(true);
+					}
+				}
+				if (placeHouses.size() > housesWithStreet){ // XXX: threshold value?
+					LongArrayList ids = new LongArrayList();
+					for (HousenumberRoad hnr : roads){
+						ids.add(hnr.getRoad().getRoadDef().getId());
+						hnr.addPlaceName(placeName);
+					}
+					if (log.isDebugEnabled())
+						log.debug("detected",placeName,"as potential address name for roads",ids);
+				} else {
+					if (log.isDebugEnabled())
+						log.debug("will ignore addr:place for address search in",placeName,"in city",cityInfo);
+				}
+			}
+		}
+	}
+
 	/**
-	 * Sorts house numbers by roads, road segments and position of the house number.
-	 * @author WanMil
+	 * Update the house number information in the interpolation intervals and 
+	 * use them to correct wrong road assignments.
+	 * @param initialHousesForRoads map that is updated when wrong road assignments were found
 	 */
-	private static class HousenumberMatchComparator implements Comparator<HousenumberMatch> {
+	private void handleInterpolationWays(MultiHashMap<MapRoad, HousenumberMatch> initialHousesForRoads) {
+		for (Entry<String, List<HousenumberIvl>> entry : interpolationWays.entrySet()){
+			List<HousenumberIvl> infos = entry.getValue();
+			for (HousenumberIvl info : infos){
+				if (info.isBad()){
+					continue;
+				}
+				boolean isOK = info.setNodeRefs(interpolationNodes, houseElems);
+				if (!isOK)
+					continue;
+				HousenumberMatch[] houses = info.getHouseNodes();
+				MapRoad uncheckedRoads[] = new MapRoad[houses.length];
+				for (int i = 0 ; i < houses.length; i++)
+					uncheckedRoads[i] = houses[i].getRoad();
+					
+				isOK = info.checkRoads();
+				if (!isOK)
+					continue;
+				// check if houses are assigned to different roads now
+				houses = info.getHouseNodes();
+				for (int i = 0 ; i < houses.length; i++){
+					if (houses[i].getRoad() != uncheckedRoads[i]){
+						initialHousesForRoads.removeMapping(uncheckedRoads[i], houses[i]);
+						if (houses[i].isIgnored() == false)
+							initialHousesForRoads.add(houses[i].getRoad(), houses[i]);
+					}
+				}
+			}
+		}
+	}
 
-		public int compare(HousenumberMatch o1, HousenumberMatch o2) {
-			if (o1 == o2) {
-				return 0;
+	private void removeDupsGroupedByCityAndName(List<HousenumberRoad> hnrList){
+		HashMap<CityInfo,MultiHashMap<String,HousenumberMatch>> cityNameHouseMap = new LinkedHashMap<>();
+		for (int i = 0; i < houseElems.size(); i++){
+			HousenumberElem house = houseElems.get(i);
+			if (house.getRoad() == null)
+				continue;
+			if (house instanceof HousenumberMatch){
+				HousenumberMatch hm = (HousenumberMatch) house;
+				if (hm.isIgnored())
+					continue;
+				HousenumberRoad hnr = hm.getHousenumberRoad();
+				if (hnr == null || hnr.getName() == null)
+					continue;
+				MultiHashMap<String, HousenumberMatch> subMap = cityNameHouseMap.get(hm.getCityInfo());
+				if (subMap == null){
+					subMap = new MultiHashMap<>();
+					cityNameHouseMap.put(hm.getCityInfo(), subMap);
+				}
+				subMap.add(hnr.getName(), hm);
+			}
+		}
+		
+		for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityNameHouseMap.entrySet()){
+			for (Entry<String, List<HousenumberMatch>> entry : topEntry.getValue().entrySet()){
+				markSimpleDuplicates(entry.getKey(), entry.getValue());
 			}
 			
-			if (o1.getRoad() != o2.getRoad()) {
-				return o1.getRoad().hashCode() - o2.getRoad().hashCode();
-			} 
+		}
+	}	
+	
+
+	private static void checkSegment(HousenumberMatch house, MapRoad road, int seg){
+		Coord cx = house.getLocation();
+		Coord c0 = road.getPoints().get(seg);
+		Coord c1 = road.getPoints().get(seg + 1);
+		double frac = getFrac(c0, c1, cx);
+		double dist = distanceToSegment(c0,c1,cx,frac);
+		if (dist < house.getDistance()){
+			house.setDistance(dist);
+			house.setRoad(road);
+			house.setSegment(seg);
+			house.setSegmentFrac(frac);
+		}
+	}
+
+
+	/**
+	 * process option --x-name-service-roads=n
+	 * The program identifies unnamed roads which are only connected to one
+	 * road with a name or to multiple roads with the same name. The process is
+	 * repeated n times. If n > 1 the program will also use unnamed roads which
+	 * are connected to unnamed roads if those are connected to named roads.
+	 * Higher values for n mean deeper search, but reasonable values are
+	 * probably between 1 and 5.
+	 * 
+	 * These roads are then used for house number processing like the named
+	 * ones. If house numbers are assigned to these roads, they are named so
+	 * that address search will find them.
+	 */
+	private void identifyServiceRoads() {
+		Int2ObjectOpenHashMap<String> roadNamesByNodeIds = new Int2ObjectOpenHashMap<>();
+		HashMap<MapRoad, List<Coord>> coordNodesUnnamedRoads = new HashMap<>();
+		HashSet<Integer> unclearNodeIds = new HashSet<>();
+
+		long t1 = System.currentTimeMillis();
+
+		List<MapRoad> unnamedRoads = new ArrayList<>();
+		for (MapRoad road : allRoads){
+			if (road.isSkipHousenumberProcessing())
+				continue;
 			
-			int dSegment = o1.getSegment() - o2.getSegment();
-			if (dSegment != 0) {
-				return dSegment;
+			if (road.getStreet() == null){
+				// if a road has a label but getStreet() returns null,
+				// the road probably has a ref. We assume these are not service roads. 
+				if (road.getName() == null){
+					unnamedRoads.add(road);
+					List<Coord> nodes = new ArrayList<>();
+					for (Coord co : road.getPoints()){
+						if (co.getId() != 0)
+							nodes.add(co);
+					}
+					coordNodesUnnamedRoads.put(road, nodes);
+				}
+			} else {
+				identifyNodes(road.getPoints(), road.getStreet(), roadNamesByNodeIds, unclearNodeIds);
 			}
-			
-			double dFrac = o1.getSegmentFrac() - o2.getSegmentFrac();
-			if (dFrac != 0d) {
-				return (int)Math.signum(dFrac);
+		}
+		int numUnnamedRoads = unnamedRoads.size();
+		long t2 = System.currentTimeMillis();
+		if (log.isDebugEnabled())
+			log.debug("identifyServiceRoad step 1 took",(t2-t1),"ms, found",roadNamesByNodeIds.size(),"nodes to check and",numUnnamedRoads,"unnamed roads" );
+		long t3 = System.currentTimeMillis();
+		int named = 0;
+		for (int pass = 1; pass <= nameSearchDepth; pass ++){
+			int unnamed = 0;
+			List<MapRoad> namedRoads = new ArrayList<>();
+			for (int j = 0; j < unnamedRoads.size(); j++){
+				MapRoad road = unnamedRoads.get(j);
+				if (road == null)
+					continue;
+				unnamed++;
+				List<Coord> coordNodes = coordNodesUnnamedRoads.get(road); 
+				String name = null;
+				for (Coord co : coordNodes){
+					if (unclearNodeIds.contains(co.getId())){
+						name = null;
+						unnamedRoads.set(j, null); // don't process again
+						break;
+					}
+					String possibleName = roadNamesByNodeIds.get(co.getId());
+					if (possibleName == null)
+						continue;
+					if (name == null)
+						name = possibleName;
+					else if (name.equals(possibleName) == false){
+						name = null;
+						unnamedRoads.set(j, null); // don't process again
+						break;
+					}
+				}
+				if (name != null){
+					named++;
+					road.setStreet(name);
+					namedRoads.add(road);
+					unnamedRoads.set(j, null); // don't process again
+				}
 			}
-			
-			double dDist = o1.getDistance() - o2.getDistance();
-			if (dDist != 0d) {
-				return (int)Math.signum(dDist);
+			for (MapRoad road : namedRoads){
+				road.setNamedByHousenumberProcessing(true);
+				String name = road.getStreet();
+				if (log.isDebugEnabled())
+					log.debug("pass",pass,"using unnamed road for housenumber processing,id=",road.getRoadDef().getId(),":",name);
+				List<Coord> coordNodes = coordNodesUnnamedRoads.get(road); 
+				identifyNodes(coordNodes, name, roadNamesByNodeIds, unclearNodeIds);
 			}
-			
-			return 0;
+
+			if (namedRoads.isEmpty())
+				break;
+			if (log.isDebugEnabled())
+				log.debug("pass",pass,unnamed,named);
 		}
-		
+		long t4 = System.currentTimeMillis();
+		if (log.isDebugEnabled()){
+			log.debug("indentifyServiceRoad step 2 took",(t4-t3),"ms, found a name for",named,"of",numUnnamedRoads,"roads" );
+		}
+		return;
 	}
-	
+
+	private void identifyNodes(List<Coord> roadPoints,
+			String streetName, Int2ObjectOpenHashMap<String> roadNamesByNodeIds, HashSet<Integer> unclearNodes) {
+		for (Coord co : roadPoints){
+			if (co.getId() != 0){
+				String prevName = roadNamesByNodeIds.put(co.getId(), streetName);
+				if (prevName != null){
+					if (prevName.equals(streetName) == false)
+						unclearNodes.add(co.getId());
+				}
+			}
+		}			
+	}
+
+
 	/**
-	 * Matches the house numbers of one street name to its OSM elements and roads. 
-	 * @param streetname name of street
-	 * @param elements a list of OSM elements belonging to this street name
-	 * @param roads a list of roads with the given street name
+	 * Find house number nodes which are parts of addr:interpolation ways.
+	 *  Check each addr:interpolation way for plausibility. 
+	 *  If the check is OK, interpolate the numbers, if not, ignore 
+	 *  also the numbers connected to the implausible way.
+	 *  
+	 *  XXX: Known problem: Doesn't work well when the road was
+	 *  clipped at the tile boundary.
+	 * @param streetName
+	 * @param housesNearCluster
+	 * @param roadsInCluster
+	 * @param road2HousenumberRoadMap 
+	 * @param interpolationInfos 
 	 */
-	private static void match(String streetname, List<Element> elements, List<MapRoad> roads) {
-		List<HousenumberMatch> numbersList = new ArrayList<HousenumberMatch>(
-				elements.size());
-		for (Element element : elements) {
-			try {
-				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);
+	private void useInterpolationInfo(String streetName,
+			List<HousenumberRoad> roadsInCluster, Map<MapRoad, HousenumberRoad> road2HousenumberRoadMap) {
+		List<HousenumberIvl> interpolationInfos = interpolationWays.get(streetName);
+		if (interpolationInfos.isEmpty())
+			return;
+		List<HousenumberMatch> housesWithIvlInfo = new ArrayList<>();
+		for (HousenumberRoad hnr : roadsInCluster){
+			for (HousenumberMatch house : hnr.getHouses()){
+				if (house.getIntervalInfoRefs() > 0)
+					housesWithIvlInfo.add(house);
 			}
 		}
+		if (housesWithIvlInfo.isEmpty())
+			return;
 		
-		MultiHashMap<MapRoad, HousenumberMatch> roadNumbers = new MultiHashMap<MapRoad, HousenumberMatch>(); 
+		HashSet<String> simpleDupCheckSet = new HashSet<>();
+		HashSet<HousenumberIvl> badIvls = new HashSet<>();
+		Long2ObjectOpenHashMap<HousenumberIvl> id2IvlMap = new Long2ObjectOpenHashMap<>();
+		Int2ObjectOpenHashMap<HousenumberMatch> interpolatedNumbers = new Int2ObjectOpenHashMap<>();
+		Int2ObjectOpenHashMap<HousenumberMatch> existingNumbers = new Int2ObjectOpenHashMap<>();
+		HashMap<HousenumberIvl, List<HousenumberMatch>> housesToAdd = new LinkedHashMap<>();
 		
-		for (HousenumberMatch n : numbersList) {
-			
-			for (MapRoad r : roads) {
-				int node = -1;
-				Coord c1 = null;
-				for (Coord c2 : r.getPoints()) {
-					if (c1 != null) {
-						Coord cx = n.getLocation();
-						double frac = getFrac(c1, c2, cx);
-						double dist = distanceToSegment(c1,c2,cx,frac);
-						if (dist <= MAX_DISTANCE_TO_ROAD && dist < n.getDistance()) {
-							n.setDistance(dist);
-							n.setSegmentFrac(frac);
-							n.setRoad(r);
-							n.setSegment(node);
+		for (HousenumberRoad hnr : roadsInCluster){
+			for (HousenumberMatch house : hnr.getHouses())
+				existingNumbers.put(house.getHousenumber(), house);
+		}
+		
+		int inCluster = 0;
+		boolean allOK = true;
+		for (HousenumberIvl hivl : interpolationInfos){
+			if (hivl.inCluster(housesWithIvlInfo) == false || hivl.ignoreForInterpolation())
+				continue;
+			++inCluster;
+			String hivlDesc = hivl.getDesc();
+			if (simpleDupCheckSet.contains(hivlDesc)){
+				// happens often in Canada (CanVec imports): two or more addr:interpolation ways with similar meaning
+				// sometimes at completely different road parts, sometimes at exactly the same
+				log.warn("found additional addr:interpolation way with same meaning, is ignored:",streetName, hivl);
+				badIvls.add(hivl);
+				allOK = false;
+				continue;
+			}
+			simpleDupCheckSet.add(hivlDesc);
+
+			id2IvlMap.put(hivl.getId(), hivl);
+			List<HousenumberMatch> interpolatedHouses = hivl.getInterpolatedHouses();
+			if (interpolatedHouses.isEmpty() == false){
+				if (interpolatedHouses.get(0).getRoad() == null){
+					// the interpolated houses are not all along one road
+					findRoadForInterpolatedHouses(streetName, interpolatedHouses, roadsInCluster);
+				}
+
+				boolean foundDup = false;
+				for (HousenumberMatch house : interpolatedHouses){
+					if (house.getRoad() == null || house.getDistance() > HousenumberIvl.MAX_INTERPOLATION_DISTANCE_TO_ROAD)
+						continue;
+					boolean ignoreGenOnly = false;
+					HousenumberMatch old = interpolatedNumbers.put(house.getHousenumber(), house);
+					if (old == null){
+						ignoreGenOnly = true;
+						old = existingNumbers.get(house.getHousenumber());
+					}
+					if (old != null){
+						// forget both or only one ? Which one?
+						house.setIgnored(true);
+						if (old.getLocation().distance(house.getLocation()) > 5){
+							foundDup = true;
+							if (!ignoreGenOnly){
+								old.setIgnored(true);
+								long ivlId = old.getElement().getOriginalId();
+								HousenumberIvl bad = id2IvlMap.get(ivlId);
+								if (bad != null)
+									badIvls.add(bad);
+							}
 						}
 					}
-					c1 = c2;
-					node++;
 				}
+				if (foundDup)
+					badIvls.add(hivl);
+				else
+					housesToAdd.put(hivl, interpolatedHouses);
 			}
-			
-			if (n.getRoad() != null) {
-				Coord c1 = n.getRoad().getPoints().get(n.getSegment());
-				Coord c2 = n.getRoad().getPoints().get(n.getSegment()+1);
-				
-				n.setLeft(isLeft(c1, c2, n.getLocation()));
-				roadNumbers.add(n.getRoad(), n);
+		}
+		if (inCluster == 0)
+			return;
+		for (HousenumberIvl badIvl: badIvls){
+			allOK = false;
+			badIvl.ignoreNodes();
+			housesToAdd.remove(badIvl);
+		}
+		Iterator<Entry<HousenumberIvl, List<HousenumberMatch>>> iter = housesToAdd.entrySet().iterator();
+		while (iter.hasNext()){
+			Entry<HousenumberIvl, List<HousenumberMatch>> entry = iter.next();
+			if (log.isInfoEnabled())
+				log.info("using generated house numbers from addr:interpolation way",entry.getKey());
+			for (HousenumberMatch house : entry.getValue()){
+				if (house.getRoad() != null && house.isIgnored() == false){
+					HousenumberRoad hnr = road2HousenumberRoadMap.get(house.getRoad());
+					if (hnr == null){
+						log.error("internal error: found no housenumber road for interpolated house",house.getElement().toBrowseURL());
+						continue;
+					}
+					hnr.addHouse(house);
+				}
 			}
 		}
+		if (log.isDebugEnabled()){
+			if (allOK)
+				log.debug("found no problems with interpolated numbers from addr:interpolations ways for roads with name",streetName);
+			else 
+				log.debug("found problems with interpolated numbers from addr:interpolations ways for roads with name",streetName);
+		}
+	}
+	
+	private static void findRoadForInterpolatedHouses(String streetName,
+			List<HousenumberMatch> houses,
+			List<HousenumberRoad> roadsInCluster) {
+		if (houses.isEmpty())
+			return;
+		Collections.sort(houses, new HousenumberMatchByNumComparator());
 		
-		// go through all roads and apply the house numbers
-		for (Entry<MapRoad, List<HousenumberMatch>> roadX : roadNumbers.entrySet()) {
-			MapRoad r = roadX.getKey();
-			if (roadX.getValue().isEmpty()) {
+		HousenumberMatch prev = null;
+		for (HousenumberMatch house : houses) {
+			if (house.isIgnored())
 				continue;
-			}
-			
-			List<HousenumberMatch> leftNumbers = new ArrayList<HousenumberMatch>();
-			List<HousenumberMatch> rightNumbers = new ArrayList<HousenumberMatch>();
-			for (HousenumberMatch hr : roadX.getValue()) {
-				if (hr.isLeft()) {
-					leftNumbers.add(hr);
-				} else {
-					rightNumbers.add(hr);
+			house.setDistance(Double.POSITIVE_INFINITY); // make sure that we don't use an old match
+			house.setRoad(null);
+			List<HousenumberMatch> matches = new ArrayList<>();
+			for (HousenumberRoad hnr : roadsInCluster){
+				MapRoad r = hnr.getRoad();
+				// make sure that we use the street info if available
+				if (house.getPlace() != null){
+					if (house.getStreet() != null && r.getStreet() != null && house.getStreet().equals(r.getStreet()) == false)
+						continue;
+				}
+				HousenumberMatch test = new HousenumberMatch(house);
+				findClosestRoadSegment(test, r);
+				if (test.getRoad() != null && test.getGroup() != null || test.getDistance() < MAX_DISTANCE_TO_ROAD){
+					matches.add(test);
 				}
 			}
+			if (matches.isEmpty()){
+				house.setIgnored(true);
+				continue;
+			}
 			
-			Collections.sort(leftNumbers, new HousenumberMatchComparator());
-			Collections.sort(rightNumbers, new HousenumberMatchComparator());
 			
-			List<Numbers> numbersListing = new ArrayList<Numbers>();
+			HousenumberMatch closest, best; 
+			best = closest  = matches.get(0);
 			
-			log.info("Housenumbers for",r.getName(),r.getCity());
-			log.info("Numbers:",roadX.getValue());
+			if (matches.size() > 1){
+				// multiple roads, we assume that the closest is the best
+				// but we may have to check the alternatives as well
+				
+				Collections.sort(matches, new HousenumberGenerator.HousenumberMatchByDistComparator());
+				closest  = matches.get(0);
+				best = checkAngle(closest, matches);	
+			}
+			house.setDistance(best.getDistance());
+			house.setSegmentFrac(best.getSegmentFrac());
+			house.setRoad(best.getRoad());
+			house.setSegment(best.getSegment());
+			for (HousenumberMatch altHouse : matches){
+				if (altHouse.getRoad() != best.getRoad() && altHouse.getDistance() < MAX_DISTANCE_TO_ROAD)
+					house.addAlternativeRoad(altHouse.getRoad());
+			}
+				
+			if (house.getRoad() == null) {
+				house.setIgnored(true);
+			} else {
+				house.calcRoadSide();
+			}
+			// plausibility check for duplicate house numbers
+			if (prev != null && prev.getHousenumber() == house.getHousenumber()){
+				// duplicate number (e.g. 10 and 10 or 10 and 10A or 10A and 10B)
+				if (prev.getSign().equals(house.getSign())){
+					prev.setDuplicate(true);
+					house.setDuplicate(true);
+				}
+			}
 			
-			int n = 0;
-			int nodeIndex = 0;
-			int lastRoutableNodeIndex = 0;
-			for (Coord p : r.getPoints()) {
-				if (n == 0) {
-					assert p instanceof CoordNode; 
+			if (house.getRoad() == null) {
+				if (house.isIgnored() == false)
+					log.warn("found no plausible road for house number element",house.getElement().toBrowseURL(),"(",streetName,house.getSign(),")");
+			}
+			if (!house.isIgnored())
+				prev = house;
+		}
+	}
+
+	
+	private static void markSimpleDuplicates(String streetName, List<HousenumberMatch> housesNearCluster) {
+		List<HousenumberMatch> sortedHouses = new ArrayList<>(housesNearCluster);
+		Collections.sort(sortedHouses, new HousenumberMatchByNumComparator());
+		int n = sortedHouses.size();
+		for (int i = 1; i < n; i++){
+			HousenumberMatch house1 = sortedHouses.get(i-1);
+			if (house1.isIgnored())
+				continue;
+			HousenumberMatch house2 = sortedHouses.get(i);
+			if (house2.isIgnored())
+				continue;
+			if (house1.getHousenumber() != house2.getHousenumber())
+				continue;
+			if (house1.getRoad() == house2.getRoad()){
+				if (house1.isFarDuplicate())
+					house2.setFarDuplicate(true);
+				continue; // handled later
+			}
+			// we have two equal house numbers in different roads
+			// check if they should be treated alike
+			boolean markFarDup = false;
+			double dist = house1.getLocation().distance(house2.getLocation());
+			if (dist > 100)
+				markFarDup = true;
+			else {
+				CityInfo city1 = house1.getCityInfo();
+				CityInfo city2 = house2.getCityInfo();
+				if (city1 != null && city1.equals(city2) == false){
+					markFarDup = true;
+				}
+			}
+			if (markFarDup){
+				if (log.isDebugEnabled())
+					log.debug("keeping duplicate numbers assigned to different roads in cluster ", streetName, house1,house2);
+				house1.setFarDuplicate(true);
+				house2.setFarDuplicate(true);
+				continue;
+			}
+			boolean ignore2nd = false;
+			if (dist < 30){
+				ignore2nd = true;
+			} else {
+				Coord c1s = house1.getRoad().getPoints().get(house1.getSegment());
+				Coord c1e = house1.getRoad().getPoints().get(house1.getSegment() + 1);
+				Coord c2s = house2.getRoad().getPoints().get(house2.getSegment());
+				Coord c2e = house2.getRoad().getPoints().get(house2.getSegment() + 1);
+				if (c1s == c2s || c1s == c2e || c1e == c2s || c1e == c2e){
+					// roads are directly connected
+					ignore2nd = true;
+				} 
+			}
+			if (ignore2nd){
+				house2.setIgnored(true);
+				if (log.isDebugEnabled()){
+					if (house1.getSign().equals(house2.getSign()))
+						log.debug("duplicate number is ignored",streetName,house2.getSign(),house2.getElement().toBrowseURL() );
+					else 
+						log.info("using",streetName,house1.getSign(), "in favor of",house2.getSign(),"as target for address search");
 				}
+			} else {
+				if (log.isDebugEnabled())
+					log.debug("keeping duplicate numbers assigned to different roads in cluster ", streetName, house1,house2);
+				house1.setFarDuplicate(true);
+				house2.setFarDuplicate(true);
+			}
+		}
+	}
 
-				// An ordinary point in the road.
-				if (p.getId() == 0) {
-					n++;
+
+	/**
+	 * If we find a sequence of house numbers like 1,3,5 or 1,2,3
+	 * where the house in the middle is assigned to a different road,
+	 * it is likely that this match is wrong.
+	 * This typically happens when a house is rather far away from two 
+	 * possible roads, but a bit closer to the wrong match. The two roads
+	 * typically form an L, U, or O shape. 
+	 * @param streetName common name tag (for debugging) 
+	 * @param sortedHouses house number elements sorted by number
+	 * @param roadNumbers the existing map which should be corrected
+	 */
+	private static void checkDubiousRoadMatches(String streetName,
+			List<HousenumberMatch> sortedHouses,
+			MultiHashMap<MapRoad, HousenumberMatch> roadNumbers) {
+		int n = sortedHouses.size();
+		for (int pos1 = 0; pos1 < n; pos1++){
+			HousenumberMatch house1 = sortedHouses.get(pos1);
+			if (house1.isIgnored() || house1.hasAlternativeRoad() == false)
+				continue;
+			int confirmed = 0;
+			int falsified = 0;
+			int pos2 = pos1;
+			HousenumberMatch bestAlternative = null;
+			double bestAlternativeDist = Double.POSITIVE_INFINITY;
+			
+			while (pos2 > 0){
+				HousenumberMatch house2 = sortedHouses.get(pos2);
+				if (house1.getHousenumber() - house2.getHousenumber() > 2)
+					break;
+					--pos2;
+			}
+			for (; pos2 < n; pos2++){
+				if (confirmed > 0)
+					break;
+				if (pos2 == pos1)
+					continue;
+				HousenumberMatch house2 = sortedHouses.get(pos2);
+				if (house2.isIgnored() || house2.getRoad() == null)
+					continue;
+				int deltaNum = house2.getHousenumber() - house1.getHousenumber();
+				if (deltaNum > 2)
+					break;
+				if (deltaNum < -2)
+					continue;
+				double distHouses = house1.getLocation().distance(house2.getLocation());
+				if (house2.getRoad() == house1.getRoad()){
+					if (Math.abs(house1.getSegment() - house2.getSegment()) < 2){
+						if (distHouses < 1.5 * bestAlternativeDist)
+							confirmed++;
+					} 
 					continue;
 				}
-
-				// The first time round, this is guaranteed to be a CoordNode
-				if (n == 0) {
-					nodeIndex++;
-					n++;
+				
+				Coord c1 = house2.getRoad().getPoints().get(house2.getSegment());
+				Coord c2 = house2.getRoad().getPoints().get(house2.getSegment()+1);
+				double frac2 = getFrac(c1,c2, house1.getLocation());
+				double dist2 = distanceToSegment(c1,c2,house1.getLocation(),frac2);
+				if (distHouses > dist2)
+					continue;
+				if (distHouses > house1.getDistance())
 					continue;
+				Coord c3 = house1.getRoad().getPoints().get(house1.getSegment());
+				Coord c4 = house1.getRoad().getPoints().get(house1.getSegment()+1);
+				if (c1 == c3 && Math.abs(Utils.getAngle(c2, c1, c4)) < 10 ||
+						c1 == c4 && Math.abs(Utils.getAngle(c2, c1, c3)) < 10 ||
+						c2 == c3 && Math.abs(Utils.getAngle(c1, c2, c4)) < 10 ||
+						c2 == c4 && Math.abs(Utils.getAngle(c1, c2, c3)) < 10){
+					confirmed++;
+					continue;
+				}
+				++falsified;
+				if (bestAlternative == null || dist2 < bestAlternativeDist){
+					bestAlternative = house2;
+					bestAlternativeDist = dist2;
 				}
+				if (log.isDebugEnabled())
+					log.debug("road check house-1:",house1.getRoad(),house1,house1.getDistance(),",house-2:", house2.getRoad(),house2,house2.getDistance(),distHouses,dist2,frac2,"house-1 is falsified");
+			}
+			if (confirmed == 0 && falsified > 0){
+				if (log.isInfoEnabled())
+					log.info("house number element assigned to road",house1.getRoad(),house1,house1.getElement().toBrowseURL(),"is closer to more plausible houses at road",bestAlternative.getRoad());
+				roadNumbers.removeMapping(house1.getRoad(), house1);
+				Coord c1 = bestAlternative.getRoad().getPoints().get(bestAlternative.getSegment());
+				Coord c2 = bestAlternative.getRoad().getPoints().get(bestAlternative.getSegment()+1);
+				double frac2 = getFrac(c1,c2, house1.getLocation());
+				double dist2 = distanceToSegment(c1,c2,house1.getLocation(),frac2);
+				if (dist2 >= MAX_DISTANCE_TO_ROAD){
+					log.info("house number element assigned to road",house1.getRoad(),house1,house1.getElement().toBrowseURL(),"is too far from more plausible road, is ignored");
+					house1.setIgnored(true);
+				} else {
+					house1.setRoad(bestAlternative.getRoad());
+					house1.setSegment(bestAlternative.getSegment());
+					house1.setSegmentFrac(frac2);
+					house1.setDistance(dist2);
+					house1.setLeft(isLeft(c1, c2, house1.getLocation()));
+					roadNumbers.add(house1.getRoad(), house1);
+				} 
+			} else if (confirmed == 0 && house1.isDuplicate()){
+				// special ?
+			}
+		}
+	}
 
-				// Now we have a CoordNode and it is not the first one.
-				Numbers numbers = new Numbers();
-				numbers.setNodeNumber(0);
-				numbers.setRnodNumber(lastRoutableNodeIndex);
-			
-				applyNumbers(numbers,leftNumbers,n,true);
-				applyNumbers(numbers,rightNumbers,n,false);
+	public static void findClosestRoadSegment(HousenumberMatch house, MapRoad r) {
+		findClosestRoadSegment(house, r, 0, r.getPoints().size());
+	}
+	
+	/**
+	 * Fill/overwrite the fields in house which depend on the assigned road.  
+	 */
+	public static void findClosestRoadSegment(HousenumberMatch house, MapRoad r, int firstSeg, int stopSeg) {
+		Coord cx = house.getLocation();
+		double oldDist = house.getDistance();
+		MapRoad oldRoad = house.getRoad();
+		house.setRoad(null);
+		house.setDistance(Double.POSITIVE_INFINITY);
+		boolean foundGroupLink = false;
+		int end = Math.min(r.getPoints().size(), stopSeg+1);
+		for (int node = firstSeg; node + 1 < end; node++){
+			Coord c1 = r.getPoints().get(node);
+			Coord c2 = r.getPoints().get(node + 1);
+			double frac = getFrac(c1, c2, cx);
+			double dist = distanceToSegment(c1,c2,cx,frac);
+			if (house.getGroup() != null && house.getGroup().linkNode == c1){
+				if (c1.highPrecEquals(c2) == false){
+					log.debug("block doesn't have zero length segment! Road:",r,house);
+				}
+				foundGroupLink = true;
+				house.setDistance(dist);
+				house.setSegmentFrac(frac);
+				house.setRoad(r);
+				house.setSegment(node);
+				break;
+			} else if (dist < house.getDistance()) {
+				house.setDistance(dist);
+				house.setSegmentFrac(frac);
+				house.setRoad(r);
+				house.setSegment(node);
+			} 
+		}
+		
+		if (house.getGroup() != null && house.getGroup().linkNode != null && foundGroupLink == false){
+			log.debug(r,house,"has a group but the link was not found, should only happen after split of zero-length-segment");
+		}
+		if (oldRoad == r){
+			if (house.getDistance() > MAX_DISTANCE_TO_ROAD + 2.5 && oldDist <= MAX_DISTANCE_TO_ROAD ){
+				log.warn("line distorted? Road segment was moved by more than",
+						String.format("%.2f m", 2.5), ", from address", r, house.getSign());
+			}
+		}
+	}
+
+	private static boolean hasChanges(
+			List<HousenumberRoad> housenumberRoads) {
+		for (HousenumberRoad hnr : housenumberRoads){
+			if (hnr.isChanged())
+				return true;
+		}
+		return false;
+	}
 
-				if (log.isInfoEnabled()) {
-					log.info("Left: ",numbers.getLeftNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getLeftStart(),"End:",numbers.getLeftEnd(), "Remaining: "+leftNumbers);
-					log.info("Right:",numbers.getRightNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getRightStart(),"End:",numbers.getRightEnd(), "Remaining: "+rightNumbers);
+	/**
+	 * 
+	 * @param housenumberRoads
+	 */
+	private static void checkWrongRoadAssignmments(List<HousenumberRoad> housenumberRoads) {
+		if (housenumberRoads.size() < 2)
+			return;
+		for (int loop = 0; loop < 10; loop++){
+			boolean changed = false;
+			for (int i = 0; i+1 < housenumberRoads.size(); i++){
+				HousenumberRoad hnr1 = housenumberRoads.get(i);
+				hnr1.setChanged(false);
+				for (int j = i+1; j < housenumberRoads.size(); j++){
+					HousenumberRoad hnr2 = housenumberRoads.get(j);
+					hnr2.setChanged(false);
+					hnr1.checkWrongRoadAssignmments(hnr2);
+					if (hnr1.isChanged()){
+						changed = true;
+						hnr1.checkIntervals();
+					}
+					if (hnr2.isChanged()){
+						changed = true;
+						hnr2.checkIntervals();
+					}
 				}
-				
-				numbersListing.add(numbers);
-				
-				lastRoutableNodeIndex = nodeIndex;
-				nodeIndex++;
-				n++;
+			}
+			if (!changed)
+				return;
+		}
+	}
+
+	/**
+	 * Sorts house numbers by roads, road segments and position of the house number.
+	 * @author WanMil
+	 */
+	public static class HousenumberMatchByPosComparator implements Comparator<HousenumberMatch> {
+
+		public int compare(HousenumberMatch o1, HousenumberMatch o2) {
+			if (o1 == o2) {
+				return 0;
+			}
+			if (o1.getRoad() == null || o2.getRoad() == null){
+				log.error("road is null in sort comparator",o1,o2);
+				throw new MapFailedException("internal error in housenumber processing"); 
+			}
+			if (o1.getRoad() != o2.getRoad()) {
+				// should not happen
+				return o1.getRoad().getRoadId() - o2.getRoad().getRoadId();
+			} 
+			
+			int dSegment = o1.getSegment() - o2.getSegment();
+			if (dSegment != 0) {
+				return dSegment;
+			}
+			
+			double dFrac = o1.getSegmentFrac() - o2.getSegmentFrac();
+			if (dFrac != 0d) {
+				return (int)Math.signum(dFrac);
 			}
 			
-			r.setNumbers(numbersListing);
+			int d = o1.getHousenumber() - o2.getHousenumber();
+			if (d != 0)
+				return d;
+			d = o1.getSign().compareTo(o2.getSign());
+			if (d != 0)
+				return d;
+			return 0;
 		}
+		
 	}
 	
 	/**
-	 * Apply the given house numbers to the numbers object.
-	 * @param numbers the numbers object to be configured
-	 * @param housenumbers a list of house numbers
-	 * @param maxSegment the highest segment number to use
-	 * @param left {@code true} the left side of the street; {@code false} the right side of the street
+	 * Sorts house numbers by house number and segment 
+	 * @author Gerd Petermann
 	 */
-	private static void applyNumbers(Numbers numbers, List<HousenumberMatch> housenumbers, int maxSegment, boolean left) {
-		NumberStyle style = NumberStyle.NONE;
-
-		if (housenumbers.isEmpty() == false) {
-			// get the sublist of housenumbers
-			int maxN = -1;
-			boolean even = false;
-			boolean odd = false;
-			for (int i = 0; i< housenumbers.size(); i++) {
-				HousenumberMatch hn = housenumbers.get(i);
-				if (hn.getSegment() >= maxSegment) {
-					break;
-				} else {
-					maxN = i;
-					if (hn.getHousenumber() % 2 == 0) {
-						even = true;
-					} else {
-						odd = true;
-					}
+	public static class HousenumberMatchByNumComparator implements Comparator<HousenumberMatch> {
+		public int compare(HousenumberMatch o1, HousenumberMatch o2) {
+			if (o1 == o2)
+				return 0;
+			int d = o1.getHousenumber() - o2.getHousenumber();
+			if (d != 0)
+				return d;
+			d = o1.getSign().compareTo(o2.getSign());
+			if (d != 0)
+				return d;
+			d = o1.getSegment() - o2.getSegment();
+			if (d != 0)
+				return d;
+			double dDist = o1.getDistance() - o2.getDistance();
+			if (dDist != 0d) {
+				return (int)Math.signum(dDist);
+			}
+			if (d != 0)
+				return d;
+			d  = Long.compare(o1.getElement().getId(), o2.getElement().getId());
+			return d;
+		}
+	}
+	/**
+	 * Sorts house numbers by distance. If eqaul, compare segment and road to produce
+	 * predictable results.  
+	 * @author Gerd Petermann
+	 */
+	public static class HousenumberMatchByDistComparator implements Comparator<HousenumberMatch> {
+		public int compare(HousenumberMatch o1, HousenumberMatch o2) {
+			if (o1 == o2)
+				return 0;
+			int d = Double.compare(o1.getDistance(), o2.getDistance());
+			if (d != 0)
+				return d;
+			d = Integer.compare(o1.getSegment(), o2.getSegment());
+			if (d != 0)
+				return d;
+			d = Integer.compare(o1.getRoad().getRoadId(), o2.getRoad().getRoadId());
+			if (d != 0)
+				return d;
+			return 0;
+		}
+	}
+	
+	private static List<HousenumberMatch> checkPlausibility(String streetName, List<MapRoad> clusteredRoads,
+			List<HousenumberMatch> housesNearCluster) {
+		int countError = 0;
+		int countTested = 0;
+		List<HousenumberMatch> failed = new ArrayList<>();
+		Int2IntOpenHashMap tested = new Int2IntOpenHashMap();
+		tested.defaultReturnValue(-1);
+		for (HousenumberMatch house : housesNearCluster){
+			if (house.isIgnored())
+				continue;
+			++countTested;
+			int num = house.getHousenumber();
+			int countPlaces = 0;
+			int countRoads = 0;
+			int prevRes = tested.get(num);
+			if (prevRes == 0)
+				continue;
+			boolean reported = false;
+			for (MapRoad r : clusteredRoads){
+				int countMatches = checkRoad(r, house.getHousenumber());
+				if (countMatches == 0)
+					continue;
+				countRoads++;
+				if (countMatches > 1){
+					log.warn(streetName,house.getSign(),house.getElement().toBrowseURL(),"is coded in",countMatches,"different road segments");
+					reported = true;
 				}
+				countPlaces += countMatches;
 			}
+			if (countPlaces == 1){
+				tested.put(num,0);
+				continue;
+			}
+			failed.add(house);
+			++countError;
 			
-			if (maxN >= 0) {
-				if (even && odd) {
-					style = NumberStyle.BOTH;
-				} else if (even) {
-					style = NumberStyle.EVEN;
-				} else {
-					style = NumberStyle.ODD;
-				}
-				
-				int start = housenumbers.get(0).getHousenumber();
-				int end = housenumbers.get(maxN).getHousenumber();
-				if (left) { 
-					numbers.setLeftStart(start);
-					numbers.setLeftEnd(end);
-				} else {
-					numbers.setRightStart(start);
-					numbers.setRightEnd(end);
+			if (countPlaces == 0 && house.getRoad() != null) {
+				log.warn(streetName, house.getSign(), house.getElement().toBrowseURL(), "is not found in expected road", house.getRoad());
+				reported = true;
+			}
+			if (countRoads > 1){
+				log.warn(streetName, house.getSign(), house.getElement().toBrowseURL(), "is coded in", countRoads, "different roads");
+				reported = true;
+			}
+			if (!reported)
+				log.error(streetName, house.getSign(), house.getElement().toBrowseURL(), "unexpected result in plausibility check, counters:",countRoads, countPlaces);
+		}
+		if (countTested == 0)
+			log.warn("plausibility check for road cluster found no valid numbers",clusteredRoads );
+		else if (countError > 0)
+			log.warn("plausibility check for road cluster failed with", countError, "detected problems:", clusteredRoads);
+		else if (log.isInfoEnabled()) 
+			log.info("plausibility check for road cluster found no problems", clusteredRoads);
+		return failed; 
+	}
+
+	/**
+	 * Count all segments that contain the house number
+	 * @param r
+	 * @param hn
+	 * @return
+	 */
+	private static int checkRoad(MapRoad r, int hn) {
+		if (r.getNumbers() == null)
+			return 0;
+
+		int matches = 0;
+		Numbers last = null;
+		Numbers firstMatch = null;
+		for (Numbers numbers : r.getNumbers()){
+			if (numbers.isEmpty())
+				continue;
+			int n = numbers.countMatches(hn);
+			if (n > 0 && firstMatch == null)
+				firstMatch = numbers;
+			
+			if (n == 1 && matches > 0){
+				if (last.getLeftEnd() == numbers.getLeftStart() && last.getLeftEnd() == hn || 
+						last.getRightEnd() == numbers.getRightStart() && last.getRightEnd() == hn ||
+						last.getLeftStart() == numbers.getLeftEnd() && last.getLeftStart() == hn||
+						last.getRightStart() == numbers.getRightEnd() && last.getRightStart() == hn){
+					n = 0; // intervals are overlapping, probably two houses (e.g. 2a,2b) at a T junction
 				}
-				
-				housenumbers.subList(0, maxN+1).clear();
 			}
+			
+			matches += n;
+			last = numbers;
 		}
+		return matches;
+	}
+
+	/**
+	 * If the closest point to a road is a junction, try to find the road
+	 * segment that forms a right angle with the house 
+	 * @param closestMatch one match that is closest to the node
+	 * @param otherMatches  the other matches with the same distance
+	 * @return the best match
+	 */
+	private static HousenumberMatch checkAngle(HousenumberMatch closestMatch,
+			List<HousenumberMatch> otherMatches) {
 		
-		if (left)
-			numbers.setLeftNumberStyle(style);
-		else
-			numbers.setRightNumberStyle(style);
-		
+		if (otherMatches.isEmpty())
+			return closestMatch;
+		HousenumberMatch bestMatch = closestMatch;
+		for (HousenumberMatch alternative : otherMatches){
+			if (alternative == closestMatch)
+				continue;
+			if (closestMatch.getDistance() < alternative.getDistance())
+				break;
+			// a house has the same distance to different road objects
+			// if this happens at a T-junction, make sure not to use the end of the wrong road 
+			Coord c1 = closestMatch.getRoad().getPoints().get(closestMatch.getSegment());
+			Coord c2 = closestMatch.getRoad().getPoints().get(closestMatch.getSegment()+1);
+			Coord cx = closestMatch.getLocation();
+			double dist = closestMatch.getDistance();
+			double dist1 = cx.distance(c1);
+			double angle, altAngle;
+			if (dist1 == dist)
+				angle = Utils.getAngle(c2, c1, cx);
+			else 
+				angle = Utils.getAngle(c1, c2, cx);
+			Coord c3 = alternative.getRoad().getPoints().get(alternative.getSegment());
+			Coord c4 = alternative.getRoad().getPoints().get(alternative.getSegment()+1);
+			
+			double dist3 = cx.distance(c3);
+			if (dist3 == dist)
+				altAngle = Utils.getAngle(c4, c3, cx);
+			else 
+				altAngle = Utils.getAngle(c3, c4, cx);
+			double delta = 90 - Math.abs(angle);
+			double deltaAlt = 90 - Math.abs(altAngle);
+			if (delta > deltaAlt){
+				bestMatch = alternative;
+				c1 = c3;
+				c2 = c4;
+			} 
+		}
+		if (log.isDebugEnabled()){
+			if (closestMatch.getRoad() != bestMatch.getRoad()){
+				log.debug("check angle: using road",bestMatch.getRoad().getRoadDef().getId(),"instead of",closestMatch.getRoad().getRoadDef().getId(),"for house number",bestMatch.getSign(),bestMatch.getElement().toBrowseURL());
+			} else if (closestMatch != bestMatch){
+				log.debug("check angle: using road segment",bestMatch.getSegment(),"instead of",closestMatch.getSegment(),"for house number element",bestMatch.getElement().toBrowseURL());
+			}
+		}
+		return bestMatch;
 	}
-	
+
 	/**
 	 * Evaluates if the given point lies on the left side of the line spanned by spoint1 and spoint2.
 	 * @param spoint1 first point of line
@@ -508,7 +1802,11 @@ public class HousenumberGenerator {
 	 * @param point the point to check
 	 * @return {@code true} point lies on the left side; {@code false} point lies on the right side
 	 */
-	private static boolean isLeft(Coord spoint1, Coord spoint2, Coord point) {
+	public static boolean isLeft(Coord spoint1, Coord spoint2, Coord point) {
+		if (spoint1.distance(spoint2) == 0){
+			log.warn("road segment length is 0 in left/right evaluation");
+		}
+
 		return ((spoint2.getHighPrecLon() - spoint1.getHighPrecLon())
 				* (point.getHighPrecLat() - spoint1.getHighPrecLat()) - (spoint2
 				.getHighPrecLat() - spoint1.getHighPrecLat())
@@ -522,7 +1820,7 @@ public class HousenumberGenerator {
 	 * @param point point
 	 * @return the distance in meter
 	 */
-	private static double distanceToSegment(Coord spoint1, Coord spoint2, Coord point, double frac) {
+	public static double distanceToSegment(Coord spoint1, Coord spoint2, Coord point, double frac) {
 
 		if (frac <= 0) {
 			return spoint1.distance(point);
@@ -535,13 +1833,15 @@ public class HousenumberGenerator {
 	}
 	
 	/**
-	 * Calculates the fraction at which the given point is closest to the line segment.
+	 * Calculates the fraction at which the given point is closest to 
+	 * the infinite line going through both points.
 	 * @param spoint1 segment point 1
 	 * @param spoint2 segment point 2
 	 * @param point point
-	 * @return the fraction
+	 * @return the fraction (can be <= 0 or >= 1 if the perpendicular is not on
+	 * the line segment between spoint1 and spoint2) 
 	 */
-	private static double getFrac(Coord spoint1, Coord spoint2, Coord point) {
+	public static double getFrac(Coord spoint1, Coord spoint2, Coord point) {
 		int aLon = spoint1.getHighPrecLon();
 		int bLon = spoint2.getHighPrecLon();
 		int pLon = point.getHighPrecLon();
@@ -565,4 +1865,235 @@ public class HousenumberGenerator {
 				return (deltaLonAP * deltaLon + (pLat - aLat) * deltaLat) / (deltaLon * deltaLon + deltaLat * deltaLat);
 		}
 	}
+
+	/**
+	 * @param length
+	 * @return string with length, e.g. "0.23 m" or "116.12 m"
+	 */
+	public static String formatLen(double length){
+		return String.format("%.2f m", length);
+	}
+	
+
+	private static class RoadPoint implements Locatable{
+		final Coord p;
+		final MapRoad r;
+		final int segment;
+		final int partOfSeg;
+		
+		public RoadPoint(MapRoad road, Coord co, int s, int part) {
+			this.p = co;
+			this.r = road;
+			this.segment = s;
+			this.partOfSeg = part;
+		}
+		@Override
+		public Coord getLocation() {
+			return p;
+		}
+		@Override
+		public String toString() {
+			return r + " " + segment + " " + partOfSeg;
+		}
+	}
+
+	/**
+	 * A performance critical part:
+	 * Index all road segments to be able to find all road segments within a given range
+	 * around a point.  
+	 * @author Gerd Petermann
+	 *
+	 */
+	class RoadSegmentIndex {
+		private final KdTree<RoadPoint> kdTree = new KdTree<>();
+		private final Int2ObjectOpenHashMap<Set<RoadPoint>> nodeId2RoadPointMap = new Int2ObjectOpenHashMap<>(); 
+		private final double range;
+		private final double maxSegmentLength;
+		private final double kdSearchRange;
+
+		public RoadSegmentIndex(List<MapRoad> roads, double rangeInMeter) {
+			this.range = rangeInMeter;
+			this.maxSegmentLength = range * 2 / 3;
+			this.kdSearchRange = Math.sqrt(Math.pow(rangeInMeter, 2) + Math.pow(maxSegmentLength/2, 2));
+			build(roads);
+			
+		}
+
+		public void build(List<MapRoad> roads){
+			for (MapRoad road : roads){
+				if (road.isSkipHousenumberProcessing())
+					continue;
+				List<Coord> points = road.getPoints();
+				if (points.size() < 2)
+					continue;
+
+				List<RoadPoint> roadPoints = new ArrayList<>();
+				RoadPoint rp;
+				for (int i = 0; i + 1 < points.size(); i++){
+					Coord c1 = points.get(i);
+					Coord c2 = points.get(i + 1);
+					int part = 0;
+					rp = new RoadPoint(road, c1, i, part++);
+					roadPoints.add(rp);
+					while (true){
+						double segLen = c1.distance(c2);
+						double frac = maxSegmentLength / segLen;
+						if (frac >= 1)
+							break;
+						// if points are not close enough, add extra point
+						c1 = c1.makeBetweenPoint(c2, frac);
+						rp = new RoadPoint(road, c1, i, part++);
+						roadPoints.add(rp);
+						segLen -= maxSegmentLength;
+					}
+				}
+				int last = points.size() - 1;
+				rp = new RoadPoint(road, points.get(last) , last, -1);
+				roadPoints.add(rp);
+				
+				Collections.shuffle(roadPoints);
+				for (RoadPoint toAdd : roadPoints){
+					int id = toAdd.p.getId();
+					if (id == 0)
+						kdTree.add(toAdd);
+					else {
+						// Coord node, add only once to KD-tree with all roads
+						Set<RoadPoint> set = nodeId2RoadPointMap.get(id);
+						if (set == null){
+							set = new LinkedHashSet<>();
+							nodeId2RoadPointMap.put(id, set);
+							kdTree.add(toAdd);
+						}
+						set.add(toAdd);
+					}  		
+				}
+			}
+		}
+		
+		public List<RoadPoint> getCLoseRoadPoints(HousenumberElem house){
+			Set<RoadPoint> closeRoadPoints = kdTree.findNextPoint(house, kdSearchRange);
+			List<RoadPoint> result = new ArrayList<>();
+			for (RoadPoint rp : closeRoadPoints){
+				int id = rp.p.getId();
+				if (id != 0)
+					result.addAll(nodeId2RoadPointMap.get(id));
+				else 
+					result.add(rp);
+			}
+			return result;
+		}
+		
+		/**
+		 * Find closest road segment and other plausible roads for a house
+		 * @param house
+		 * @return null if no road was found, else a {@link HousenumberMatch} instance 
+		 */
+		public HousenumberMatch createHousenumberMatch(HousenumberElem house){
+			HousenumberMatch closest = new HousenumberMatch(house);
+			List<RoadPoint> closeRoadPoints = getCLoseRoadPoints(house);
+			if (closeRoadPoints.isEmpty())
+				return closest;
+			Collections.sort(closeRoadPoints, new Comparator<RoadPoint>() {
+				// sort by distance (smallest first)
+				public int compare(RoadPoint o1,  RoadPoint o2) {
+					if (o1 == o2)
+						return 0;
+					int d = Integer.compare(o1.r.getRoadId(), o2.r.getRoadId());
+					if (d != 0)
+						return d;
+					d = Integer.compare(o1.segment, o2.segment);
+					if (d != 0)
+						return d; 
+					return Integer.compare(o1.partOfSeg, o2.partOfSeg);
+				}
+			});
+
+			List<HousenumberMatch> matches = new ArrayList<>(40);
+			BitSet testedSegments = new BitSet();
+			MapRoad lastRoad = null;
+			HousenumberMatch hnm = null;
+			for (RoadPoint rp : closeRoadPoints){
+				if (house.getStreet() != null && house.getStreet().equals(rp.r.getStreet()) == false){
+					if (rp.r.getStreet() != null){
+						continue;
+					}
+				}
+				if (rp.r != lastRoad){
+					hnm = new HousenumberMatch(house);
+					testedSegments.clear();
+					matches.add(hnm);
+					lastRoad = rp.r;
+				}
+				double oldDist = hnm.getDistance();
+				if (rp.partOfSeg >= 0){
+					// rp.p is at start or before end of segment 
+					if (testedSegments.get(rp.segment) == false){
+						testedSegments.set(rp.segment);
+						checkSegment(hnm, rp.r, rp.segment);
+					}
+				} 
+				if (rp.partOfSeg < 0){
+					// rp is at end of road, check (also) the preceding segment 
+					if (rp.segment < 1){
+						log.error("internal error: trying to use invalid roadPoint",rp);
+					} else if (testedSegments.get(rp.segment - 1) == false){
+						testedSegments.set(rp.segment-1);
+						checkSegment(hnm, rp.r, rp.segment-1);
+					}
+				}
+				if (oldDist == hnm.getDistance())
+					continue;
+			}
+			if (matches.isEmpty())
+				return closest;
+			Collections.sort(matches, new HousenumberGenerator.HousenumberMatchByDistComparator());
+			closest = matches.get(0);
+			closest = checkAngle(closest, matches);
+			closest.calcRoadSide();
+			HousenumberMatch bestMatchingName = null; 
+			if (closest.getStreet() != null && closest.getStreet().equals(closest.getRoad().getStreet()))
+				bestMatchingName = closest;
+			for (HousenumberMatch altHouse : matches){
+				if (altHouse.getDistance() >= MAX_DISTANCE_TO_ROAD)
+					break;
+				if (altHouse.getRoad() != closest.getRoad()){
+					if (house.getStreet() != null && altHouse.getDistance() > closest.getDistance()){
+						if (house.getStreet().equals(altHouse.getRoad().getStreet())){
+							if (bestMatchingName == null || bestMatchingName.getDistance() > altHouse.getDistance())
+								bestMatchingName = altHouse;
+						} else {
+							if (bestMatchingName != null && altHouse.getDistance() > bestMatchingName.getDistance())
+								continue;
+						}
+					}
+					closest.addAlternativeRoad(altHouse.getRoad());
+				}
+			}
+			if (closest == bestMatchingName || bestMatchingName == null || bestMatchingName.getDistance() > MAX_DISTANCE_TO_ROAD)
+				return closest;
+			
+			double ratio = closest.getDistance() / bestMatchingName.getDistance();
+			if (ratio < 0.25)
+				return closest;
+			HousenumberMatch best = closest;
+			if (ratio > 0.75){
+				// prefer the road with the matching name
+				for (MapRoad r : closest.getAlternativeRoads()){
+					if (house.getStreet().equals(r.getStreet()))
+						bestMatchingName.addAlternativeRoad(r);
+				}
+				best = bestMatchingName;
+				best.calcRoadSide();
+			} else {
+				if (log.isDebugEnabled()){
+					log.debug("further checks needed for address", closest.getStreet(), closest.getSign(), closest.getElement().toBrowseURL(), 
+							formatLen(closest.getDistance()), formatLen(bestMatchingName.getDistance()));
+				}
+
+			}
+			return best;
+		}
+	}
 }
+
+
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGroup.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGroup.java
new file mode 100644
index 0000000..f09df40
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGroup.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * 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.housenumber;
+
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.filters.LineSplitterFilter;
+import uk.me.parabola.mkgmap.general.MapRoad;
+
+/**
+ * Combine two or more HousenumberMatch instances that 
+ * can be found at the same point on the road
+ * (with a tolerance of a few meters).
+ * We will create a single Numbers instance for them.
+ * 
+ * @author Gerd Petermann
+ *
+ */
+public class HousenumberGroup {
+
+	private static final Logger log = Logger.getLogger(HousenumberGroup.class);
+	final HousenumberRoad hnr;
+	final List<HousenumberMatch> houses = new ArrayList<>();
+	Int2IntOpenHashMap usedNumbers;
+	int minNum,  maxNum;
+	int minSeg , maxSeg;
+	double minFrac, maxFrac;
+	HousenumberMatch closestHouseToRoad ;
+	HousenumberMatch farthestHouseToRoad;
+	int odd,even;
+	Coord linkNode;
+	boolean findSegmentWasCalled;
+	
+	public HousenumberGroup(HousenumberRoad hnr, List<HousenumberMatch> housesToUse) {
+		this.hnr = hnr;
+		reset();
+		for (HousenumberMatch house : housesToUse){
+			addHouse(house);
+		}
+	}
+	
+	private void addHouse(HousenumberMatch house){
+		int num = house.getHousenumber();
+		if (num % 2 == 0)
+			++even;
+		else 
+			++odd;
+		int count = usedNumbers.get(num);
+		usedNumbers.put(num, count + 1);
+		
+		if (houses.isEmpty()){
+			minNum = maxNum = house.getHousenumber();
+			minSeg = maxSeg = house.getSegment();
+			minFrac = maxFrac = house.getSegmentFrac();
+			closestHouseToRoad = farthestHouseToRoad = house;
+		} else {
+			if (house.getSegment() < minSeg){
+				minSeg = house.getSegment();
+				minFrac = house.getSegmentFrac();
+			} else if (house.getSegment() > maxSeg){
+				maxSeg = house.getSegment();
+				maxFrac = house.getSegmentFrac();
+			} else if (house.getSegment() == minSeg ){
+				minFrac = Math.min(minFrac, house.getSegmentFrac());
+			} else if (house.getSegment() == maxSeg ){
+				maxFrac = Math.max(maxFrac, house.getSegmentFrac());
+			}
+			minNum = Math.min(minNum, num);
+			maxNum = Math.max(maxNum, num);
+			if (house.getDistance() < closestHouseToRoad.getDistance())
+				closestHouseToRoad = house;
+			if (house.getDistance() > farthestHouseToRoad.getDistance())
+				farthestHouseToRoad = house;
+		}
+		houses.add(house);
+	}
+	
+	private static final double MIN_DISTANCE_TO_EXISTING_POINT = 7.5;
+	
+	/**
+	 * find place for the group, change to or add number nodes
+	 * @param nodesForLinks 
+	 * @return true if one or two nodes were added
+	 */
+	public boolean findSegment(String streetName, List<HousenumberGroup> groups){
+		if (minSeg < 0 || maxSeg < 0){
+			log.error("internal error: group is not valid:",this);
+			return false;
+		}
+		findSegmentWasCalled = true;
+		linkNode = null;
+		List<Coord> points = getRoad().getPoints();
+		Coord pointToUse = null;
+		int seg = closestHouseToRoad.getSegment();
+		Coord c1 = points.get(seg);
+		Coord c2 = points.get(seg + 1);
+		if (c1.highPrecEquals(c2)){
+			boolean useExisting = true;
+			// already a zero length segment
+			for (HousenumberGroup hg : groups){
+				if (hg == this)
+					continue;
+				if (hg.linkNode == c1){
+					if (hg.closestHouseToRoad.isLeft() != this.closestHouseToRoad.isLeft()){
+						// attach this group to the same segment on the other road side
+						linkNode = c1;
+						return false;
+					} else {
+						log.warn("two groups on same side of road share same point, group 1:",hg,"group 2:",this,"in road",getRoad());
+						useExisting = false;
+						break;
+					}
+				}
+			}
+			if (useExisting){
+				c1.setNumberNode(true);
+				c2.setNumberNode(true);
+				linkNode = c1;
+				return false;
+			}
+		}
+		int timesToAdd = 1;
+		double frac = closestHouseToRoad.getSegmentFrac();
+		if (frac < 0) frac = 0;
+		if (frac > 1) frac = 1;
+		double segLen = c1.distance(c2);
+		pointToUse = c1.makeBetweenPoint(c2, frac);
+		double len1 = segLen * frac;
+		double len2 = (1 - Math.min(1, frac)) * segLen;
+		if (Math.min(len1, len2) < MIN_DISTANCE_TO_EXISTING_POINT){
+			pointToUse = (len1 <= len2) ? c1 : c2;
+		} else {
+			Coord optPoint = ExtNumbers.rasterLineNearPoint(c1, c2, pointToUse, true);
+			double opt1Dist = c1.distance(optPoint);
+			double opt2Dist = c2.distance(optPoint);
+			pointToUse = optPoint;
+			if (Math.min(opt1Dist, opt2Dist) <= MIN_DISTANCE_TO_EXISTING_POINT){
+				pointToUse = (opt1Dist < opt2Dist) ? c1 : c2;
+			}
+			else {
+				timesToAdd = 2;
+			}
+		}
+		if (points.size() + timesToAdd > LineSplitterFilter.MAX_POINTS_IN_LINE)
+			return false;
+		pointToUse.setNumberNode(true);
+		if (timesToAdd == 2){
+			// add two new points between c1 and c2
+			points.add(seg + 1, pointToUse);
+			pointToUse = new Coord (pointToUse);
+			pointToUse.setNumberNode(true);
+			points.add(seg + 1, pointToUse);
+			linkNode = pointToUse;
+		} else {
+		// copy it
+			pointToUse = new Coord(pointToUse);
+			pointToUse.setNumberNode(true);
+			// add copy before c2 
+			points.add(seg + 1, pointToUse);
+			if (pointToUse.highPrecEquals(c1)){
+				linkNode = c1;
+			} else { 
+				// link to the copy of c2 which is before c2
+				linkNode = pointToUse;
+			}
+		}
+		return true;
+	}
+
+	public boolean verify(){
+		if (findSegmentWasCalled)
+			return true;
+		
+		if (minSeg < 0 || maxSeg < 0)
+			return false;
+		int step = 1;
+		if (odd == 0 || even == 0)
+			step = 2;
+		boolean ok = false;
+		if (usedNumbers.size() == (maxNum - minNum) / step + 1)
+			ok = true;
+
+		// final check: 
+		double deltaDist = Math.abs(closestHouseToRoad.getDistance() - farthestHouseToRoad.getDistance());
+		if (houses.size() > 2 &&  deltaDist < houses.size() * 3 ){
+			// more than two houses: make sure that they are really not parallel to the road 
+			// for each house we calculate 3m so that a group is kept if it forms an angle of 45° or more
+			// with the road, presuming that the road is rather straight
+			ok = false;
+		}
+		for (HousenumberMatch house : houses){
+			// forget the group, it will not improve search
+			house.setGroup(ok ? this : null);
+		}
+		return ok;
+	}
+	
+	public MapRoad getRoad(){
+		return hnr.getRoad();
+	}
+	
+	private final static double CLOSE_HOUSES_DIST = 10;
+	public static boolean housesFormAGroup(HousenumberMatch house1, HousenumberMatch house2) {
+		if (house1.isIgnored() || house2.isIgnored())
+			return false;
+		if (house1.getRoad() != house2.getRoad()){
+			log.error("internal error, group check with houses on different roads?",house1.getElement().getId(),house2.getElement().getId());
+			return false;
+		}
+//		assert house1.getRoad() == house2.getRoad();
+		
+
+		if (house1.getSegment() > house2.getSegment()){
+			HousenumberMatch help = house1;
+			house1 = house2;
+			house2 = help;
+		}
+		double distBetweenHouses = house1.getLocation().distance(house2.getLocation());
+		if (distBetweenHouses == 0)
+			return true;
+		double minDistToRoad = Math.min(house1.getDistance(), house2.getDistance());
+		double maxDistToRoad = Math.max(house1.getDistance(), house2.getDistance());
+		double distOnRoad = house2.getDistOnRoad(house1);
+		
+		if (house1.getSegment() != house2.getSegment()){
+			if (minDistToRoad > 40 && distBetweenHouses < CLOSE_HOUSES_DIST)
+				return true;
+			
+			// not the same segment, the distance on road may be misleading when segments have a small angle 
+			// and the connection point is a bit more away 
+			Coord c1 = house1.getLocation();
+			Coord c2 = house2.getLocation();
+			Coord closest1 = house1.getClosestPointOnRoad();
+			Coord closest2 = house2.getClosestPointOnRoad();
+			double frac1 = HousenumberGenerator.getFrac(closest1, closest2, c1);
+			double frac2 = HousenumberGenerator.getFrac(closest1, closest2, c2);
+			double segLen = closest1.distance(closest2);
+			if (frac1 < 0) frac1 = 0;
+			if (frac2 < 0) frac2 = 0;
+			if (frac1 > 1) frac1 = 1;
+			if (frac2 > 1) frac2 = 1;
+			double distOnRoadSimple = (Math.max(frac1, frac2) - Math.min(frac1, frac2)) * segLen;
+			if (distOnRoadSimple != distOnRoad){
+//				log.debug("distOnRoad recalculation:", house1.getRoad(),house1,house2,distOnRoad,"--->",distOnRoadSimple);
+				distOnRoad = distOnRoadSimple;
+			}
+		}
+		if (distOnRoad <= 0){
+			return true;
+		}
+		
+		// two houses form a group when the distance on road is short
+		// how short? The closer the houses are to the road, the shorter
+		double toleranceDistOnRoad = 5 + maxDistToRoad/ 10;
+		
+		if (distOnRoad > toleranceDistOnRoad){
+			return false;
+		}
+		
+		double deltaDistToRoad = maxDistToRoad - minDistToRoad;
+		double ratio2 = deltaDistToRoad / distBetweenHouses;
+		// a ratio2 near or higher 1 means that the two houses and the closest point on the 
+		// road are on a straight line
+		if (ratio2 > 0.9)
+			return true;
+		if (ratio2 < 0.666)
+			return false;
+		return true;
+	}
+
+	public boolean tryAddHouse(HousenumberMatch house) {
+		if (house.isInterpolated() || house.getRoad() == null || house.isIgnored())
+			return false;
+		int num = house.getHousenumber();
+		int step = 1;
+		if (odd == 0 || even == 0)
+			step = 2;
+		if (num - maxNum != step)
+			return false;
+		HousenumberMatch last = houses.get(houses.size()-1);
+		if (last.getGroup() != null){ 
+			if (last.getGroup() == house.getGroup()){
+				addHouse(house);
+				return true;
+			} else 
+				return false;
+		}
+		if (last.getDistance() + 3 < house.getDistance() && last.isDirectlyConnected(house)){
+			addHouse(house);
+			return true;
+		}
+			
+		if (housesFormAGroup(house, last) == false){
+			return false;
+		}
+		if (houses.size() > 1){
+			HousenumberMatch first = houses.get(0);
+			if (housesFormAGroup(house, first) == false){
+				HousenumberMatch preLast = houses.get(houses.size()-2);
+				double angle = Utils.getAngle(house.getLocation(), last.getLocation(), preLast.getLocation());
+ 				if (Math.abs(angle) > 30)
+					return false;
+			}
+		}
+		addHouse(house);
+		return true;
+	}
+
+
+	public boolean recalcPositions(){
+		List<HousenumberMatch> saveHouses = new ArrayList<>(houses);
+		reset();
+		for (HousenumberMatch house : saveHouses)
+			addHouse(house);
+		if (!verify()){
+			for (HousenumberMatch house : houses){
+				HousenumberGenerator.findClosestRoadSegment(house, getRoad());
+			}
+			return false;
+		}
+		return true;
+	}
+
+	private void reset() {
+		usedNumbers = new Int2IntOpenHashMap();
+		minNum = Integer.MAX_VALUE; 
+		maxNum = -1;
+		minSeg = Integer.MAX_VALUE;
+		maxSeg = -1;
+		minFrac = maxFrac = Double.NaN;
+		closestHouseToRoad = null;
+		farthestHouseToRoad = null;
+		odd = even = 0;
+		houses.clear();
+	}
+
+	public String toString(){
+		return houses.toString();
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberIvl.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberIvl.java
new file mode 100644
index 0000000..8d62400
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberIvl.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * 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.housenumber;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.MapRoad;
+import uk.me.parabola.mkgmap.reader.osm.Node;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+
+/**
+ * Represents a (part of an) addr:interpolation way.
+ * It contains the points between two house number elements
+ * and the information how numbers are interpolated along
+ * the way that is described by these points. 
+ * 
+ * We try to use the information to find 
+ * a) the right road for the houses
+ * b) the right road segment 
+ * c) the position of the interpolated houses
+ * 
+ * We have to be aware of several special cases so we use some
+ * flags to say for which of the above points the information
+ * can be used.
+ * 
+ * @author Gerd Petermann
+ *
+ */
+public class HousenumberIvl {
+	private static final Logger log = Logger.getLogger(HousenumberIvl.class);
+	
+	/** Gives the maximum distance between house number element and the matching road 
+	 * when the number is part of an addr:interpolation way */
+	public static final double MAX_INTERPOLATION_DISTANCE_TO_ROAD = 75.0;
+
+	private final String streetName;
+	private final Way interpolationWay;
+	private MapRoad roadForInterpolatedHouses;
+	private final Node n1,n2;
+	private List<Coord> points;
+	private int step, start, end, steps;
+	private HousenumberMatch[] knownHouses = {null, null}; 
+	
+	private boolean hasMultipleRoads;
+	private boolean foundCluster;
+	private int interpolated; // counter to detect wrong double use 
+	private boolean ignoreForInterpolation;
+
+	private boolean equalEnds;
+	private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street");
+	private static final short housenumberTagKey = TagDict.getInstance().xlate("mkgmap:housenumber");		
+	private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation");
+
+	
+	public HousenumberIvl(String steetName, Way interpolationWay, Node n1, Node n2) {
+		this.streetName = steetName;
+		this.interpolationWay = interpolationWay;
+		this.n1 = n1;
+		this.n2 = n2;
+	}
+	
+	public void setPoints(List<Coord> points) {
+		this.points = new ArrayList<Coord>(points);
+	}
+	public void setStep(int step) {
+		this.step = step;
+	}
+	public int getStep() {
+		return step;
+	}
+	public void setStart(int start) {
+		this.start = start;
+	}
+
+	public int getStart() {
+		return start;
+	}
+	public void setEnd(int end) {
+		this.end = end;
+	}
+	public int getEnd() {
+		return end;
+	}
+	public void setSteps(int steps) {
+		this.steps = steps;
+	}
+
+
+	public Node getNode1() {
+		return n1;
+	}
+
+	public Node getNode2() {
+		return n2;
+	}
+
+//	public boolean needsSplit(){
+//		return needsSplit;
+//	}
+	
+	public void addHousenumberMatch(HousenumberMatch house) {
+		if (house.getElement() == n1)
+			knownHouses[0] = house;
+		else if (house.getElement() == n2)
+			knownHouses[1] = house;
+		else {
+			log.error("cannot add",house,"to",this);
+		}
+	}
+	
+	public boolean checkRoads(){
+		boolean res = checkRoads2();
+		if (!res || equalEnds){
+			// the interval is not ok --> ignore the numbers as well
+			ignoreNodes();
+		}  
+		return res;
+	}
+	
+	private boolean checkRoads2(){
+		for (int i = 0; i < 2; i++){
+			if (knownHouses[i] == null ){
+				log.error("internal error: housenumber matches not properly set", this);
+				return false;
+			}
+			if (knownHouses[i].getRoad() == null || knownHouses[i].getDistance() > 100 ){
+				log.warn("cannot find any reasonable road for both nodes, ignoring them",streetName,this);
+				return false;
+			}
+		}
+		if (knownHouses[0].getRoad().getRoadDef().getId() == knownHouses[1].getRoad().getRoadDef().getId()){
+			if (knownHouses[0].getRoad() != knownHouses[1].getRoad()){
+				// special case: interval goes along clipped road, data is probably OK
+				hasMultipleRoads = true;
+				return true;
+			}
+			for (MapRoad r : knownHouses[0].getAlternativeRoads()){
+				if (r.getRoadDef().getId() == knownHouses[0].getRoad().getRoadDef().getId()){
+					// special case: interval may go along clipped road, data is probably OK
+					hasMultipleRoads = true;
+					return true;
+				}
+			}			
+		}
+		MapRoad bestRoad = null;
+		// make sure that the closest road is one with a matching name
+		for (int i = 0; i < 2; i++){
+			while (streetName.equals(knownHouses[i].getRoad().getStreet()) == false && knownHouses[i].hasAlternativeRoad()){
+				HousenumberMatch testx = new HousenumberMatch(knownHouses[i]);
+				MapRoad r = knownHouses[i].getAlternativeRoads().remove(0);
+				HousenumberGenerator.findClosestRoadSegment(testx, r);
+				if (testx.getDistance() < MAX_INTERPOLATION_DISTANCE_TO_ROAD){
+					copyRoadData(testx, knownHouses[i]);
+				}
+			}
+		}
+		List<MapRoad> toTest = new ArrayList<>();
+		toTest.add(knownHouses[0].getRoad());
+		toTest.add(knownHouses[1].getRoad());
+		for (MapRoad r : knownHouses[0].getAlternativeRoads()){
+			if (knownHouses[1].getAlternativeRoads().contains(r))
+				toTest.add(r);
+		}
+		HousenumberMatch[] test = new HousenumberMatch[2];
+		HousenumberMatch[] closest = new HousenumberMatch[2];
+		boolean foundSingleRoad = false;
+		for (MapRoad r : toTest){
+			if (streetName.equals(r.getStreet()) == false)
+				continue;
+			foundSingleRoad = true;
+			for (int i = 0; i < 2; i++){
+				test[i] = knownHouses[i];
+				if (test[i].getRoad() != r){
+					test[i] = new HousenumberMatch(knownHouses[i]);
+					HousenumberGenerator.findClosestRoadSegment(test[i], r);
+					test[i].calcRoadSide();
+				}
+				if (test[i].getRoad() == null || test[i].getDistance() > MAX_INTERPOLATION_DISTANCE_TO_ROAD ){
+					foundSingleRoad = false;
+					break;
+				}
+			}
+			if (foundSingleRoad){
+				if (test[0].isLeft() != test[1].isLeft()){
+					foundSingleRoad = false;
+					continue;
+				}
+				int s0 = test[0].getSegment();
+				int s1 = test[1].getSegment();
+				// check if the road and the addr:interpolation way are nearly parallel lines
+				double angle1 = Utils.getAngle(test[0].getClosestPointOnRoad(), points.get(0),points.get(1));
+				if (Math.abs(angle1) < 30){
+					foundSingleRoad = false;
+					HousenumberMatch testx = new HousenumberMatch(test[0]);
+					for (int s = Math.min(s0,s1); s <= Math.max(s0, s1); s++){
+						if (s != test[0].getSegment()){
+							HousenumberGenerator.findClosestRoadSegment(testx, r, s,s+1);
+							angle1 = Utils.getAngle(testx.getClosestPointOnRoad(), points.get(0),points.get(1));
+							if (Math.abs(angle1) >= 30 && testx.getDistance() < 2*test[0].getDistance()){
+								test[0] = testx;
+								foundSingleRoad = true;
+								break;
+							}
+						}
+					}
+				}
+				double angle2 = Utils.getAngle(points.get(points.size()-2),points.get(points.size()-1),test[1].getClosestPointOnRoad());
+				if (Math.abs(angle2) < 30){
+					foundSingleRoad = false;
+					HousenumberMatch testx = new HousenumberMatch(test[1]);
+					for (int s = Math.min(s0,s1); s <= Math.max(s0, s1); s++){
+						if (s != test[1].getSegment()){
+							HousenumberGenerator.findClosestRoadSegment(testx, r, s,s+1);
+							angle2 = Utils.getAngle(points.get(points.size()-2),points.get(points.size()-1),testx.getClosestPointOnRoad());
+							if (Math.abs(angle2) >= 30 && testx.getDistance() < 2*test[1].getDistance()){
+								test[1] = testx;
+								foundSingleRoad = true;
+								break;
+							}
+						}
+					}
+				}
+			}
+			if (foundSingleRoad){
+				if (r.isNamedByHousenumberProcessing() == false)
+					break;
+				// the closest road was originally unnamed , try to find one that is named in OSM 
+				if (bestRoad == null){
+					bestRoad = r;
+					closest[0] = test[0];
+					closest[1] = test[1];
+				}
+			}
+		}
+		if (!foundSingleRoad && bestRoad != null){
+			// not matching road name in original OSM data, use the closest
+			foundSingleRoad = true;
+			test[0] = closest[0];
+			test[1] = closest[1];
+		}
+		if (!foundSingleRoad){
+			if (streetName.equals(knownHouses[0].getRoad().getStreet()) == false || streetName.equals(knownHouses[1].getRoad().getStreet()) == false){
+				log.warn("cannot find reasonable road for both nodes",streetName,this);
+				return false;
+			}
+			hasMultipleRoads = true;
+			return true;
+		}
+		// we found the road that should be used for interpolation
+		roadForInterpolatedHouses = test[0].getRoad();
+
+		// we found a single plausible road, make sure that both nodes are using it
+		for (int i = 0; i < 2; i++){
+			if (knownHouses[i].getRoad() != test[i].getRoad() || knownHouses[i].getSegment() != test[i].getSegment()){
+				copyRoadData(test[i], knownHouses[i]);
+				knownHouses[i].forgetAlternativeRoads();
+			}
+			if (knownHouses[i].getSegmentFrac() < 0 || knownHouses[i].getSegmentFrac() > 1){
+				hasMultipleRoads = true;
+			}
+		}
+		if (knownHouses[0].isLeft() != knownHouses[1].isLeft()){
+			log.warn("addr:interpolation way crosses road",streetName,this);
+			return false;
+		}
+		return true;
+	}
+
+	private void copyRoadData(HousenumberMatch source, HousenumberMatch dest) {
+		if (log.isInfoEnabled()){
+			if (source.getRoad() != dest.getRoad())
+				log.info("moving",streetName,dest.getSign(),dest.getElement().toBrowseURL(),"from road",dest.getRoad(),"to road",source.getRoad());
+			else 
+				log.info("moving",streetName,dest.getSign(),dest.getElement().toBrowseURL(),"from segment",dest.getSegment(),"to ",source.getSegment(),"in road",source.getRoad());
+		}
+		dest.setRoad(source.getRoad());
+		dest.setSegment(source.getSegment());
+		dest.setSegmentFrac(source.getSegmentFrac());
+		dest.setDistance(source.getDistance());
+		dest.calcRoadSide();
+	}
+	
+	public List<HousenumberMatch> getInterpolatedHouses(){
+		List<HousenumberMatch> houses = new ArrayList<>();
+		if (ignoreForInterpolation|| start == end || steps <= 0)
+			return houses;
+		List<Coord> interpolatedPoints = getInterpolatedPoints();
+		int usedStep = (start < end) ? step : -step;
+		int hn = start;
+		boolean distanceWarningIssued = false;
+		for (Coord co : interpolatedPoints){
+			hn += usedStep;
+			Node generated = new Node(interpolationWay.getId(), co);
+			generated.setFakeId();
+			generated.addTag(streetTagKey, streetName);
+			String number = String.valueOf(hn);
+			generated.addTag(housenumberTagKey, number);
+			// TODO: maybe add check that city info and zip code of both houses is equal ?
+			// what if not ?
+			HousenumberElem houseElem = new HousenumberElem(generated, knownHouses[0].getCityInfo());
+			houseElem.setHousenumber(hn);
+			houseElem.setZipCode(knownHouses[0].getZipCode());
+			houseElem.setStreet(streetName);
+			houseElem.setSign(number);
+			HousenumberMatch house = new HousenumberMatch(houseElem);
+			if (roadForInterpolatedHouses != null){
+				HousenumberGenerator.findClosestRoadSegment(house, roadForInterpolatedHouses);
+				if (house.getRoad() == null || house.getDistance() > MAX_INTERPOLATION_DISTANCE_TO_ROAD ){
+					if (distanceWarningIssued == false){
+						log.warn("interpolated house is not close to expected road",this,house);
+						distanceWarningIssued = true;
+					}
+					continue;
+				}
+				house.calcRoadSide();
+			}
+			house.setInterpolated(true);
+			houses.add(house);
+		}
+		if (getId() == 37881402){
+			long dd = 4;
+		}
+		
+		if (log.isDebugEnabled()){
+			String addrInterpolationMethod = interpolationWay.getTag(addrInterpolationTagKey);
+			if (hasMultipleRoads == false)
+				log.debug(this,"generated",addrInterpolationMethod,"interpolated number(s) for",knownHouses[0].getRoad());
+			else 
+				log.debug(this,"generated",addrInterpolationMethod,"interpolated number(s) for",streetName);
+		}
+		return houses;
+	}
+	/**
+	 * Calculate the wanted number of coords on a way so that they have
+	 * similar distances to each other (and to the first and last point 
+	 * of the way).
+	 * @param points list of points that build the way
+	 * @param num the wanted number 
+	 * @return a list with the number of points or the empty list in 
+	 * case of errors
+	 */
+	public List<Coord> getInterpolatedPoints(){
+		if (interpolated > 0){
+			log.debug("interpolating numbers again for", this );
+		}
+		interpolated++;
+		if (steps < 1 || points.size() < 2)
+			return Collections.emptyList();
+
+		List<Coord> interpolated = new ArrayList<>(steps);
+		double wayLen = 0;
+		for (int i = 0; i+1 < points.size(); i++){
+			wayLen += points.get(i).distance(points.get(i+1));
+		}
+		double ivlLen = wayLen / (steps+1);
+		if (ivlLen < 0.1){
+			if (log.isInfoEnabled())
+				log.info("addr:interpolation",interpolationWay.toBrowseURL(),"segment ignored, would generate",steps,"houses with distance of",ivlLen,"m");
+			return interpolated;
+		}
+		int pos = 0;
+		double rest = 0;
+		while (pos+1 < points.size()){
+			Coord c1 = points.get(pos);
+			Coord c2 = points.get(pos+1);
+			pos++;
+			double neededPartOfSegment = 0;
+			double segmentLen = c1.distance(c2);
+			for(;;){
+				neededPartOfSegment += ivlLen - rest;
+				if (neededPartOfSegment <= segmentLen){
+					double fraction = neededPartOfSegment / segmentLen;
+					Coord c = c1.makeBetweenPoint(c2, fraction);
+					interpolated.add(c);
+					if (interpolated.size() >= steps){
+//						GpxCreator.createGpx("e:/ld/road", knownHouses[0].getRoad().getPoints());
+//						GpxCreator.createGpx("e:/ld/test", interpolated, Arrays.asList(points.get(0),points.get(points.size()-1)));
+						return interpolated;
+					}
+					rest = 0;
+				} else {
+					rest = segmentLen - neededPartOfSegment + ivlLen;
+					break;
+				}
+			}
+			
+		}
+		log.warn("addr:interpolation",interpolationWay.toBrowseURL(),"interpolation for segment with nodes",n1.getId(),n2.getId(),"failed");
+		return interpolated;
+	}
+	
+	public String toString() {
+		return interpolationWay.toBrowseURL() + " " + start + ".." + end + ", step=" + step;
+	}
+
+	public String getDesc() {
+		return streetName + "_" + start + ".." + end + "_" + step;
+	}
+
+//	public boolean setNodeRefs(HashMap<Element, HousenumberMatch> houses) {
+//		knownHouses[0] = houses.get(n1);
+//		knownHouses[1] = houses.get(n2);
+//		if (knownHouses[0] == null || knownHouses[1] == null)
+//			return false;
+//		knownHouses[0].incIntervalInfoRefs();
+//		knownHouses[1].incIntervalInfoRefs();
+//		return true;
+//	}
+//
+	public void ignoreNodes() {
+		for (int i = 0; i < 2; i++){
+			if (knownHouses[i] != null){
+				knownHouses[i].decIntervalInfoRefs();
+				if (knownHouses[i].getIntervalInfoRefs() == 0)
+					knownHouses[i].setIgnored(true);
+			}
+		}
+	}
+
+	public long getId() {
+		return interpolationWay.getId();
+	}
+
+	
+	public boolean ignoreForInterpolation() {
+		return ignoreForInterpolation;
+	}
+
+	public void setIgnoreForInterpolation(boolean ignoreForInterpolation) {
+		this.ignoreForInterpolation = ignoreForInterpolation;
+	}
+
+	public boolean isBad() {
+		return false;
+	}
+
+
+	public boolean inCluster(List<HousenumberMatch> housesNearCluster) {
+		int count = 0;
+		for (HousenumberMatch house : housesNearCluster){
+			if (knownHouses[0] == house || knownHouses[1] == house){
+				++count;
+				
+			}
+			if (count == 2)
+				break;
+		}
+		if (count > 0){
+			foundCluster = true;
+			return true;
+		}
+		return false;
+	}
+
+	public boolean foundCluster() {
+		return foundCluster;
+	}
+
+	public void setEqualEnds() {
+		this.equalEnds = true;
+		
+	}
+
+	public boolean setNodeRefs(Map<Long, Integer> interpolationNodes,
+			List<HousenumberElem> houseElems) {
+		for (int i = 0; i < 2; i++){
+			long id = (i == 0) ? n1.getId(): n2.getId();
+			Integer elemPos = interpolationNodes.get(id);
+			if (elemPos == null || elemPos >= houseElems.size())
+				return false;
+			HousenumberElem he = houseElems.get(elemPos);
+			if (he instanceof HousenumberMatch == false)
+				return false;
+			if (he.getElement().getId() != id)
+				return false;
+			knownHouses[i] = (HousenumberMatch) he;
+			knownHouses[i].incIntervalInfoRefs();
+		}
+		return true;
+	}
+	
+	public HousenumberMatch[] getHouseNodes (){
+		return knownHouses;
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java
index 7422b2d..3caee8e 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java
@@ -13,25 +13,22 @@
 
 package uk.me.parabola.mkgmap.osmstyle.housenumber;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.general.MapRoad;
-import uk.me.parabola.mkgmap.reader.osm.Element;
-import uk.me.parabola.mkgmap.reader.osm.Node;
-import uk.me.parabola.mkgmap.reader.osm.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.Way;
+import uk.me.parabola.util.Locatable;
 
 /**
  * Stores the matching data between a housenumber and its road.
  * @author WanMil
  */
-public class HousenumberMatch {
-
-	private final Element element;
-	
+public class HousenumberMatch extends HousenumberElem implements Locatable {
 	private MapRoad road;
+	private HousenumberRoad housenumberRoad;
 	
 	private double distance = Double.POSITIVE_INFINITY;
 	private int segment = -1;
@@ -39,104 +36,19 @@ public class HousenumberMatch {
 	
 	private double segmentFrac;
 	
-	private int housenumber;
-	
-	/**
-	 * Instantiates a new housenumber match element.
-	 * @param element the OSM element tagged with mkgmap:housenumber
-	 * @throws IllegalArgumentException if the housenumber cannot be parsed
-	 */
-	public HousenumberMatch(Element element) {
-		this.element = element;
-		parseHousenumber();
-	}
-	
-	/**
-	 * Retrieves the location of the housenumber.
-	 * @return location of housenumber
-	 */
-	public Coord getLocation() {
-		return (element instanceof Node ? ((Node)element).getLocation() : ((Way)element).getCofG());
-	}
-	
-	/**
-	 * Retrieves the house number of this element.
-	 * @param e an OSM element
-	 * @return the house number (or {@code null} if no house number set)
-	 */
-	private static final short housenumberTagKey1 =  TagDict.getInstance().xlate("mkgmap:housenumber");
-	private static final short housenumberTagKey2 =  TagDict.getInstance().xlate("addr:housenumber");
-	public static String getHousenumber(Element e) {
-		String res = e.getTag(housenumberTagKey1); 
-		if (res != null)
-			return res;
-		return e.getTag(housenumberTagKey2);
-	}
+	private boolean ignored;
+	private boolean isDuplicate;
+	private boolean interpolated;
+	private int moved;
+	// distance in m between closest point on road and the point that is found in the address search
+	private double searchDist = Double.NaN;
+	private boolean isFarDuplicate;
+	private HousenumberGroup group;
+	private List<MapRoad> alternativeRoads;
+	private int intervalInfoRefs; // counter
 	
-	/**
-	 * Parses the house number string. It accepts the first positive number part
-	 * of a string. So all leading and preceding non number parts are ignored.
-	 * So the following strings are accepted:
-	 * <table>
-	 * <tr>
-	 * <th>Input</th>
-	 * <th>Output</th>
-	 * </tr>
-	 * <tr>
-	 * <td>23</td>
-	 * <td>23</td>
-	 * </tr>
-	 * <tr>
-	 * <td>-23</td>
-	 * <td>23</td>
-	 * </tr>
-	 * <tr>
-	 * <td>21-23</td>
-	 * <td>21</td>
-	 * </tr>
-	 * <tr>
-	 * <td>Abc 21</td>
-	 * <td>21</td>
-	 * </tr>
-	 * <tr>
-	 * <td>Abc 21.45</td>
-	 * <td>21</td>
-	 * </tr>
-	 * <tr>
-	 * <td>21 Main Street</td>
-	 * <td>21</td>
-	 * </tr>
-	 * <tr>
-	 * <td>Main Street</td>
-	 * <td><i>IllegalArgumentException</i></td>
-	 * </tr>
-	 * </table>
-	 * @throws IllegalArgumentException if parsing fails
-	 */
-	private void parseHousenumber() {
-		String housenumberString = getHousenumber(element);
-		
-		if (housenumberString == null) {
-			throw new IllegalArgumentException("No housenumber found in "+element.toBrowseURL());
-		}
-		
-		// the housenumber must match against the pattern <anything>number<notnumber><anything>
-		Pattern p = Pattern.compile("\\D*(\\d+)\\D?.*");
-		Matcher m = p.matcher(housenumberString);
-		if (m.matches() == false) {
-			throw new IllegalArgumentException("No housenumber ("+element.toBrowseURL()+"): "+housenumberString);
-		}
-		try {
-			// get the number part and parse it
-			housenumber = Integer.parseInt(m.group(1));
-		} catch (NumberFormatException exp) {
-			throw new IllegalArgumentException("No housenumber ("+element.toBrowseURL()+"): "+housenumberString);
-		}
-
-		// a housenumber must be > 0
-		if (housenumber <= 0) {
-			throw new IllegalArgumentException("No housenumber ("+element.toBrowseURL()+"): "+housenumberString);
-		}
+	public HousenumberMatch(HousenumberElem he) {
+		super(he);
 	}
 
 	public MapRoad getRoad() {
@@ -209,31 +121,208 @@ public class HousenumberMatch {
 		this.segmentFrac = segmentFrac;
 	}
 
-	/**
-	 * Retrieve the house number
-	 * @return the house number
-	 */
-	public int getHousenumber() {
-		return housenumber;
+	public boolean hasAlternativeRoad() {
+		return alternativeRoads != null && alternativeRoads.isEmpty() == false;
+	}
+
+	public boolean isIgnored() {
+		return ignored;
+	}
+
+	public void setIgnored(boolean ignored) {
+		this.ignored = ignored;
+	}
+
+	public boolean isDuplicate() {
+		return isDuplicate;
+	}
+
+	public void setDuplicate(boolean isDuplicate) {
+		this.isDuplicate = isDuplicate;
+	}
+
+	public boolean isInterpolated() {
+		return interpolated;
+	}
+
+	public void setInterpolated(boolean interpolated) {
+		this.interpolated = interpolated;
+	}
+
+	public int getMoved() {
+		return moved;
+	}
+
+	public void incMoved() {
+		this.moved++;
+	}
+
+	public double getSearchDist() {
+		return searchDist;
+	}
+
+	public void setSearchDist(double searchDist) {
+		this.searchDist = searchDist;
+	}
+
+	public String toString() {
+		String s1 = String.valueOf(getHousenumber());
+		if (getSign().length() > 2 + s1.length())
+			return s1 + "("+segment+")";
+		return getSign() + "("+segment+")";
+	}
+
+	public void setFarDuplicate(boolean b) {
+		this.isFarDuplicate = b;
+		
+	}
+
+	public boolean isFarDuplicate() {
+		return isFarDuplicate;
 	}
 
 	/**
-	 * Set the house number.
-	 * @param housenumber house number
+	 * @return either an existing point on the road
+	 * or the calculated perpendicular. In the latter case
+	 * the highway count is zero.
+	 *   
 	 */
-	public void setHousenumber(int housenumber) {
-		this.housenumber = housenumber;
+	public Coord getClosestPointOnRoad(){
+		if (segmentFrac <= 0)
+			return getRoad().getPoints().get(segment);
+		if (segmentFrac >= 1)
+			return getRoad().getPoints().get(segment+1);
+		Coord c1 = getRoad().getPoints().get(segment);
+		Coord c2 = getRoad().getPoints().get(segment+1);
+		return c1.makeBetweenPoint(c2, segmentFrac);
 	}
 
 	/**
-	 * Retrieve the OSM element that defines the house number.
-	 * @return the OSM element
+	 * @param other a different house on the same road
+	 * @return  the distance in m between the perpendiculars on the road
+	 * of two houses.
 	 */
-	public Element getElement() {
-		return element;
+	public double getDistOnRoad(HousenumberMatch other) {
+		if (getRoad() != other.getRoad()){
+			assert false : "cannot compute distance on road for different roads"; 
+		}
+		List<Coord> points = getRoad().getPoints();
+		HousenumberMatch house1 = this;
+		HousenumberMatch house2 = other;
+		if (house1.segment > house2.segment || house1.segment == house2.segment && house1.segmentFrac > house2.segmentFrac){
+			house1 = other;
+			house2 = this;
+		}
+		int s1 = house1.segment;
+		int s2 = house2.segment;
+		double distOnRoad = 0;
+		while (s1 < s2){
+			double segLen = points.get(s1).distance(points.get(s1 + 1));
+			if (s1 == house1.getSegment() && house1.getSegmentFrac() > 0){
+				// rest of first segment
+				distOnRoad += Math.max(0, 1-house1.getSegmentFrac()) * segLen;
+			} else
+				distOnRoad += segLen;
+			s1++;
+		}
+		double segLen = points.get(s1).distance(points.get(s1 + 1));
+		if (house2.getSegmentFrac() > 0)
+			distOnRoad += Math.min(1, house2.getSegmentFrac()) * segLen;
+		if (house1.getSegmentFrac() > 0 && s1 == house1.segment)
+			distOnRoad -= Math.min(1, house1.getSegmentFrac()) * segLen;
+		return distOnRoad;
+	}
+
+	public HousenumberRoad getHousenumberRoad() {
+		return housenumberRoad;
+	}
+
+	public void setHousenumberRoad(HousenumberRoad housenumberRoad) {
+		this.housenumberRoad = housenumberRoad;
 	}
 	
-	public String toString() {
-		return String.valueOf(housenumber)+"("+segment+")";
+	public void setGroup(HousenumberGroup housenumberBlock) {
+		this.group = housenumberBlock;
+	}
+
+	public HousenumberGroup getGroup() {
+		return group;
+	}
+
+	public void addAlternativeRoad(MapRoad road2) {
+		if (alternativeRoads == null){
+			alternativeRoads = new ArrayList<>();
+		}
+		alternativeRoads.add(road2);
 	}
+	public List<MapRoad> getAlternativeRoads() {
+		if (alternativeRoads == null)
+			return Collections.emptyList();
+		return alternativeRoads;
+	}
+	public void forgetAlternativeRoads(){
+		alternativeRoads = null;
+	}
+
+	public int getIntervalInfoRefs() {
+		return intervalInfoRefs;
+	}
+
+	public void incIntervalInfoRefs() {
+		intervalInfoRefs++;
+	}
+
+	public void decIntervalInfoRefs() {
+		if (intervalInfoRefs > 0)
+			--intervalInfoRefs;
+	}
+
+	public boolean isDirectlyConnected(HousenumberMatch other){
+		if (getElement() instanceof Way && other.getElement() instanceof Way){
+			List<Coord> s1 = ((Way) getElement()).getPoints();
+			List<Coord> s2 = ((Way) other.getElement()).getPoints();
+			for (int i = 0; i+1 < s1.size(); i++){
+			    Coord co = s1.get(i);
+			    co.setPartOfShape2(false);
+			}
+			for (int i = 0; i+1 < s2.size(); i++){
+			    Coord co = s2.get(i);
+			    co.setPartOfShape2(true);
+			}
+			for (int i = 0; i+1 < s1.size(); i++){
+			    Coord co = s1.get(i);
+			    if (co.isPartOfShape2())
+			    	return true;
+			}
+		}
+		return false;
+	}
+
+
+	public void calcRoadSide(){
+		if (getRoad() == null)
+			return;
+		Coord c1 = getRoad().getPoints().get(getSegment());
+		Coord c2 = getRoad().getPoints().get(getSegment()+1);
+		setLeft(HousenumberGenerator.isLeft(c1, c2, getLocation()));
+		
+	}
+	
+	public boolean isEqualAddress(HousenumberElem other){
+		if (getRoad() != other.getRoad())
+			return false;
+		if (getSign().equals(other.getSign()) == false)
+			return false;
+		if (getZipCode() != null && other.getZipCode() != null){
+			if (getZipCode().equals(other.getZipCode()) == false)
+				return false;
+		}
+		if (getCityInfo() != null && other.getCityInfo() != null){
+			if (getCityInfo().equals(other.getCityInfo()) == false)
+				return false;
+		}
+		return true;
+	}
+	
 }
+
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberRoad.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberRoad.java
new file mode 100644
index 0000000..92f3618
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberRoad.java
@@ -0,0 +1,803 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * 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.housenumber;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.CoordNode;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.CityInfo;
+import uk.me.parabola.mkgmap.general.MapRoad;
+import uk.me.parabola.mkgmap.general.ZipCodeInfo;
+import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByNumComparator;
+import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByPosComparator;
+
+/**
+ * Helper class to combine house numbers with MapRoad instances
+ * @author Gerd Petermann
+ *
+ */
+public class HousenumberRoad {
+	private static final Logger log = Logger.getLogger(HousenumberRoad.class);
+	private String streetName;
+	private final MapRoad road;
+	private CityInfo roadCityInfo;
+	private ZipCodeInfo roadZipCode;
+	private ExtNumbers extNumbersHead;
+	private final List<HousenumberMatch> houseNumbers;
+	private boolean changed;
+	private boolean isRandom;
+	private boolean removeGaps;
+	private LinkedHashSet<String> furtherNames;
+	
+	
+	public HousenumberRoad(MapRoad r, CityInfo ci, List<HousenumberMatch> potentialNumbersThisRoad) {
+		this.streetName = r.getStreet();
+		this.road = r;
+		this.roadCityInfo = ci;
+		this.houseNumbers = new ArrayList<>(potentialNumbersThisRoad);
+		for (HousenumberMatch house : houseNumbers){
+			house.setHousenumberRoad(this);
+		}
+	}
+
+	
+	public void addPlaceName(String name) {
+		if (furtherNames == null){
+			furtherNames = new LinkedHashSet<>();
+		}
+		furtherNames.add(name);
+	}
+
+	public String getName (){
+		return streetName; 
+	}
+	
+	public void buildIntervals() {
+		Collections.sort(houseNumbers, new HousenumberMatchByNumComparator());
+		if (log.isInfoEnabled())
+			log.info("Initial housenumbers for",road,"in",road.getCity(),houseNumbers);
+		
+		filterRealDuplicates();
+		filterGroups();
+		if (houseNumbers.isEmpty())
+			return;
+		List<HousenumberMatch> leftNumbers = new ArrayList<HousenumberMatch>();
+		List<HousenumberMatch> rightNumbers = new ArrayList<HousenumberMatch>();
+		
+		for (HousenumberMatch house : houseNumbers) {
+			if (house.getRoad() == null || house.isIgnored()){
+				continue;
+			}
+			if (house.getHousenumberRoad() != this || house.getHousenumberRoad().getRoad() != house.getRoad()){
+				log.error("internal error, road links are not correct",house.getElement().toBrowseURL());
+			}
+			if (house.isLeft()) {
+				leftNumbers.add(house);
+			} else {
+				rightNumbers.add(house);
+			}
+		}
+		detectGroups(leftNumbers, rightNumbers);
+		Collections.sort(leftNumbers, new HousenumberMatchByPosComparator());
+		Collections.sort(rightNumbers, new HousenumberMatchByPosComparator());
+		
+		
+		int currNodePos = 0;
+		int nodeIndex = 0;
+		int prevNumberNodeIndex = 0;
+		int prevNodePos = 0;
+		extNumbersHead = null;
+		ExtNumbers currNumbers = null;
+		for (Coord p : road.getPoints()) {
+			if (currNodePos == 0) {
+				if (road.skipAddToNOD() == false)
+					assert p instanceof CoordNode; 
+			}
+
+			// An ordinary point in the road.
+			if (p.isNumberNode() == false) {
+				currNodePos++;
+				continue;
+			}
+
+			// The first time round, this is guaranteed to be a CoordNode
+			if (currNodePos == 0) {
+				nodeIndex++;
+				currNodePos++;
+				continue;
+			}
+
+			// Now we have a CoordNode and it is not the first one.
+			ExtNumbers numbers = new ExtNumbers(this);
+			numbers.setNodeIndex(prevNumberNodeIndex);
+			int leftUsed = numbers.setNumbers(leftNumbers, prevNodePos, currNodePos, true);
+			int rightUsed = numbers.setNumbers(rightNumbers, prevNodePos, currNodePos, false);
+			prevNodePos = currNodePos;
+			// maintain chain
+			numbers.prev = currNumbers;
+			if (currNumbers != null)
+				currNumbers.next = numbers;
+			else {
+				extNumbersHead = numbers;
+			}
+			currNumbers = numbers;
+			leftNumbers.subList(0, leftUsed).clear();
+			rightNumbers.subList(0, rightUsed).clear(); 				
+
+			prevNumberNodeIndex = nodeIndex;
+			nodeIndex++;
+			currNodePos++;
+		}
+	}
+
+	/**
+	 * Try to detect groups of houses with continues numbers 
+	 * which should be attached to a zero-length segment.
+	 * Very useful when a service road connects eg. 
+	 * numbers 7..15 to the named road, but also for just two numbers.
+	 * @param depth
+	 * @param leftNumbers
+	 * @param rightNumbers
+	 */
+	private void detectGroups(List<HousenumberMatch> leftNumbers, List<HousenumberMatch> rightNumbers) {
+		List<HousenumberGroup> groups = new ArrayList<>();
+
+		for (int side = 0; side < 2; side++){
+			boolean left = side == 0;
+			List<HousenumberMatch> houses = left ? leftNumbers : rightNumbers;
+			HousenumberGroup group = null;
+			for (int j = 1; j < houses.size(); j++){
+				HousenumberMatch house = houses.get(j);
+				if (group == null){
+					if (house.isInterpolated())
+						continue;
+					HousenumberMatch predHouse = houses.get(j-1);
+					int deltaNum = predHouse.getHousenumber() - house.getHousenumber();
+					if (Math.abs(deltaNum) > 2)
+						continue;
+					if (HousenumberGroup.housesFormAGroup(predHouse, house))
+						group = new HousenumberGroup(this, houses.subList(j-1, j+1));
+				} else {
+					if (group.tryAddHouse(house) == false){
+						if(group.verify())
+							groups.add(group);
+						group = null;
+					}
+				}
+			}
+			if (group != null && group.verify()){
+				groups.add(group);
+			}
+		}
+		if (groups.isEmpty())
+			return;
+		boolean nodesAdded = false;
+		for (HousenumberGroup group : groups){
+			int oldNumPoints = getRoad().getPoints().size(); 
+			if (nodesAdded){
+				if (group.recalcPositions() == false)
+					continue;
+			}
+			if (group.findSegment(streetName, groups)){
+				nodesAdded = true;
+				if (log.isDebugEnabled())
+					log.debug("added",getRoad().getPoints().size() - oldNumPoints,"number node(s) at",group.linkNode.toDegreeString(),"for group",group,"in road",getRoad());
+				oldNumPoints = getRoad().getPoints().size();
+				int minSeg = group.minSeg;
+				for (HousenumberMatch house : this.houseNumbers){
+					if (house.getSegment() >= minSeg)
+						HousenumberGenerator.findClosestRoadSegment(house, getRoad());
+				}
+				group.recalcPositions();
+			} else {
+				if(group.linkNode != null){
+					if (log.isDebugEnabled())
+						log.debug("used existing zero-length-segment at",group.linkNode.toDegreeString(),"for group",group,"in road",getRoad());
+				}
+			}
+		}
+		return;
+	}
+
+	/**
+	 */
+	public void checkIntervals(){
+		if (extNumbersHead == null)
+			return;
+		boolean anyChanges = false;
+		
+		extNumbersHead.detectRandom();
+		for (int loop = 0; loop < 10; loop++){
+			if (loop > 4){
+				// TODO: 3,4,5 ? 
+				setRandom(true);
+			}
+			setChanged(false);
+			extNumbersHead = extNumbersHead.checkSingleChainSegments(streetName, removeGaps);
+			extNumbersHead = extNumbersHead.checkChainPlausibility(streetName, houseNumbers);
+			if (isChanged())
+				anyChanges = true;
+			else 
+				break;
+		}
+		setChanged(anyChanges);
+	}
+	
+	
+	/**
+	 * Identify duplicate numbers and ignore those which are close together
+	 * and those which are probably wrong. 
+	 */
+	private void filterRealDuplicates() {
+		List<HousenumberMatch> toIgnore = new ArrayList<>();
+		final int TO_SEARCH = 6;
+		int oddLeft = 0, oddRight = 0, evenLeft = 0, evenRight = 0;
+		for (HousenumberMatch  house: houseNumbers){
+			if (house.isIgnored())
+				continue;
+			if (house.isLeft()){
+				if (house.getHousenumber() % 2 == 0) 
+					evenLeft++;
+				else 
+					oddLeft++;
+			} else {
+				if (house.getHousenumber() % 2 == 0)
+					evenRight++;
+				else
+					oddRight++;
+			}
+		}
+		HousenumberMatch usedForCalc = null;
+		for (int i = 1; i < houseNumbers.size(); i++){
+			HousenumberMatch house1 = houseNumbers.get(i - 1);
+			HousenumberMatch house2 = houseNumbers.get(i);
+			if (house1.getSign().equals(house2.getSign()) == false){
+				usedForCalc = null;
+			} else {
+				if (house1.isEqualAddress(house2) == false)
+					continue;
+				// found a duplicate address (e.g. 2 and 2 or 1b and 1b in same road,city etc.)
+				double distBetweenHouses = house2.getLocation().distance(house1.getLocation());
+				double distToUsed = (usedForCalc == null) ? distBetweenHouses : house2.getLocation().distance(usedForCalc.getLocation()); 
+				if (usedForCalc == null)
+					usedForCalc = (house1.getDistance() < house2.getDistance()) ? house1 : house2;
+				else {
+					house1 = usedForCalc;
+				}
+				boolean sameSide = (house2.isLeft() == house1.isLeft());
+				if (log.isDebugEnabled())
+					log.debug("analysing duplicate address",streetName,house1.getSign(),"for road with id",getRoad().getRoadDef().getId());
+				if (sameSide && (distBetweenHouses < 100 || distToUsed < 100)){
+					HousenumberMatch obsolete = house1 == usedForCalc ? house2 : house1;
+					if (log.isDebugEnabled())
+						log.debug("house",obsolete,obsolete.getElement().toBrowseURL(),"is close to other element and on the same road side, is ignored");
+					toIgnore.add(obsolete);
+					continue;
+				}
+				
+				if (!sameSide){
+					if (log.isDebugEnabled())
+						log.debug("oddLeft, oddRight, evenLeft, evenRight:",oddLeft, oddRight, evenLeft, evenRight);
+					HousenumberMatch wrongSide = null;
+					if (house2.getHousenumber() % 2 == 0){
+						if (evenLeft == 1 && (oddLeft > 1 || evenRight > 0 && oddRight == 0)){
+							wrongSide = house2.isLeft() ? house2: house1;
+						}
+						if (evenRight == 1 && (oddRight > 1 || evenLeft > 0 && oddLeft == 0)){
+							wrongSide = !house2.isLeft() ? house2: house1;
+						}
+					} else {
+						if (oddLeft == 1 && (evenLeft > 1 || oddRight > 0 && evenRight == 0)){
+							wrongSide = house2.isLeft() ? house2: house1;
+						}
+						if (oddRight == 1 && (evenRight > 1 || oddLeft > 0 && evenLeft == 0)){
+							wrongSide = !house2.isLeft() ? house2: house1;
+						}
+					}
+					if (wrongSide != null){
+						if (log.isDebugEnabled())
+							log.debug("house",streetName,wrongSide.getSign(),"from",wrongSide.getElement().toBrowseURL(),"seems to be wrong, is ignored");
+						toIgnore.add(wrongSide);
+						continue;
+					}
+				}
+				
+				double[] sumDist = new double[2];
+				double[] sumDistSameSide = new double[2];
+				int[] confirmed = new int[2];
+				int[] falsified = new int[2];
+				int[] found = new int[2];
+				List<HousenumberMatch> dups = Arrays.asList(house2, house1);
+				for (int k = 0; k < dups.size(); k++){
+					HousenumberMatch other, curr;
+					if (k == 0){
+						curr = dups.get(0);
+						other = dups.get(1);
+					} else {
+						curr = dups.get(1);
+						other = dups.get(0);
+					}
+					int pos = houseNumbers.indexOf(curr);
+
+					int left = pos - 1;
+					int right = pos + 1;
+					HousenumberMatch nearHouse;
+					int stillToFind = TO_SEARCH;
+					while (stillToFind > 0){
+						int oldDone = stillToFind;
+						if (left >= 0){
+							nearHouse = houseNumbers.get(left);
+							if (nearHouse != other){
+								double dist = curr.getLocation().distance(nearHouse.getLocation());
+								sumDist[k] += dist;
+								if (nearHouse.isLeft() == curr.isLeft()){
+									sumDistSameSide[k] += dist;
+								}
+								if (curr.getHousenumber() == nearHouse.getHousenumber()){
+									if (dist < 20)
+										confirmed[k]++;
+								} else {
+									if (dist < 10 )
+										falsified[k]++;
+								}
+							}
+							--left;
+							stillToFind--;
+							if (stillToFind == 0)
+								break;
+						}
+						if (right < houseNumbers.size()){
+							nearHouse = houseNumbers.get(right);
+							if (nearHouse != other){
+								double dist = curr.getLocation().distance(nearHouse.getLocation());
+								sumDist[k] += dist;
+								if (nearHouse.isLeft() == curr.isLeft()){
+									sumDistSameSide[k] += dist;
+								}
+								if (curr.getHousenumber() == nearHouse.getHousenumber()){
+									if (dist < 40)
+										confirmed[k]++;
+								} else {
+									if (dist < 10 )
+										falsified[k]++;
+								}
+							}
+							stillToFind--;
+							right++;
+						}
+						if (oldDone == stillToFind)
+							break;
+					}
+					found[k] = TO_SEARCH - 1 - stillToFind; 
+				}
+				if (log.isDebugEnabled()){
+					log.debug("dup check 1:", streetName, house1, house1.getElement().toBrowseURL());
+					log.debug("dup check 2:", streetName, house2, house2.getElement().toBrowseURL());
+					log.debug("confirmed",Arrays.toString(confirmed),"falsified",Arrays.toString(falsified),"sum-dist",Arrays.toString(sumDist),"sum-dist-same-side",Arrays.toString(sumDistSameSide));
+				}
+				HousenumberMatch bad = null;
+				if (confirmed[1] > 0 && confirmed[0] == 0  && falsified[1] == 0)
+					bad = dups.get(0);
+				else if (confirmed[0] > 0 && confirmed[1] == 0  && falsified[0] == 0)
+					bad = dups.get(1);
+				else if (found[0] > 3 && sumDist[0] > sumDist[1] && sumDistSameSide[0] > sumDistSameSide[1])
+					bad = dups.get(0);
+				else if (found[1] > 3 && sumDist[1] > sumDist[0] && sumDistSameSide[1] > sumDistSameSide[0])
+					bad = dups.get(1);
+				if (bad != null){
+					toIgnore.add(bad);
+				} else {
+					if (log.isDebugEnabled())
+						log.debug("duplicate house number, don't know which one to use, ignoring both");
+					toIgnore.add(house1);
+					toIgnore.add(house2);
+					house2.setIgnored(true);
+					house1.setIgnored(true);
+				}
+			}
+		} 
+		for (HousenumberMatch house : toIgnore){
+			if (log.isInfoEnabled())
+				log.info("duplicate housenumber",streetName,house.getSign(),"is ignored for road with id",house.getRoad().getRoadDef().getId(),",house:",house.getElement().toBrowseURL());
+			houseNumbers.remove(house);
+		}
+	}
+
+	/**
+	 * Identify groups of buildings with numbers like 1a,1b,1c.
+	 * The list in housenumbers is sorted so that 2 appears before 2a and
+	 * 2b appears before 2c. 
+	 * XXX This is quite aggressive, maybe we have to add more logic here.  
+	 */
+	private void filterGroups() {
+		if (houseNumbers.size() <= 1)
+			return;
+		HousenumberMatch prev = houseNumbers.get(0);
+		HousenumberMatch used = null;
+		for (int i = 1; i < houseNumbers.size(); i++){
+			HousenumberMatch house = houseNumbers.get(i);
+			if (house.getHousenumber() != prev.getHousenumber())
+				used = null;
+			else {
+				if (used == null)
+					used = prev;
+				if (prev.getSign().equals(house.getSign()) &&  prev.isEqualAddress(house) == false){
+					// we want to keep these duplicates 
+				} else {
+					house.setIgnored(true);
+					if (log.isInfoEnabled())
+						log.info("using",streetName,used.getSign(), "in favor of",house.getSign(),"as target for address search");
+				}
+			}
+			prev = house;
+		}
+	}
+
+	public void checkWrongRoadAssignmments(HousenumberRoad other) {
+		if (this.extNumbersHead == null || other.extNumbersHead == null)
+			return;
+		
+		for (int loop = 0; loop < 10; loop++){
+			boolean changed = false;
+			ExtNumbers head1 = this.extNumbersHead;
+			for (ExtNumbers en1 = head1; en1 != null; en1 = en1.next){
+				if (changed)
+					break;
+				if (en1.hasNumbers() == false)
+					continue;
+				ExtNumbers head2 = other.extNumbersHead;
+				for (ExtNumbers en2 = head2; en2 != null; en2 = en2.next){
+					if (changed)
+						break;
+					if (en2.hasNumbers() == false)
+						continue;
+					int res = ExtNumbers.checkIntervals(streetName, en1, en2);
+					switch (res) {
+					case ExtNumbers.OK_NO_CHANGES:
+					case ExtNumbers.NOT_OK_KEEP:
+						break;
+					case ExtNumbers.OK_AFTER_CHANGES:
+						changed = true;
+						this.setChanged(true);
+						other.setChanged(true);
+						break;
+					case ExtNumbers.NOT_OK_TRY_SPLIT:
+						if (en1.needsSplit()){
+							ExtNumbers test = en1.tryChange(ExtNumbers.SR_FIX_ERROR);
+							if (test != en1){
+								changed = true;
+								if (test.prev == null){
+									this.extNumbersHead = test;
+								}
+							}
+						}
+						if (en2.needsSplit()){
+							ExtNumbers test = en2.tryChange(ExtNumbers.SR_FIX_ERROR);
+							if (test != en2){
+								changed = true;
+								if (test.prev == null){
+									other.extNumbersHead = test;
+								}
+							}
+						}
+						break;
+					case ExtNumbers.NOT_OK_STOP:
+						return;
+					default:
+						log.error("can't fix",en1,en2);
+					}
+				}
+			}
+			if (!changed)
+				break;
+		}
+	}
+	
+	public void setNumbers() {
+		if (extNumbersHead == null)
+			return;
+		if (houseNumbers.isEmpty())
+			return;
+		// make sure that the name we used for the cluster is also attached to the road
+		if (streetName == null){
+			log.error("found no name for road with housenumbers, implement a move to the next named road ?",road);
+			return;
+		}
+		String[] labels = road.getLabels();
+		boolean found = false;
+		for (String label : labels){
+			if (label == null)
+				break;
+			if (streetName.equals(label))
+				found = true;
+		}
+		if (!found){
+			if (labels[0] == null){
+				// add empty label so that the address search name doesn't appear in the map
+				// when the original road did not have any label
+				labels[0] = "";
+			}
+			for (int i = 1; i < labels.length; i++){
+				if (labels[i] == null){
+					labels[i] = streetName;
+					log.info("added label",streetName,"for",road,"Labels are now:",Arrays.toString(labels));
+					found = true;
+					break;
+				}
+			}
+		}
+		if (!found){
+			int last = labels.length-1;
+			String droppedLabel = labels[last];
+			labels[last] = streetName;
+			if (droppedLabel != null){
+				if (log.isInfoEnabled())
+					log.info("dropped label",droppedLabel,"for",road,"in preference to correct address search. Labels are now:",Arrays.toString(labels));
+			}
+		}
+		if (furtherNames != null){
+			boolean changed = false;
+			for (String furtherName : furtherNames){
+				if (road.addLabel(furtherName))
+					changed = true;
+			}
+			if (changed){
+				log.info("added further labels for",road,"Labels are now:",Arrays.toString(labels));
+			}
+		}
+
+		if (road.getZip() == null && roadZipCode != null){
+			road.setZip(roadZipCode.getZipCode());
+		}
+		road.setNumbers(extNumbersHead.getNumberList());
+		
+	}
+	
+	public MapRoad getRoad(){
+		return road;
+	}
+
+	public CityInfo getRoadCityInfo() {
+		return roadCityInfo;
+	}
+
+	public ZipCodeInfo getRoadZipCode() {
+		return roadZipCode;
+	}
+
+
+	public boolean isChanged() {
+		return changed;
+	}
+
+	public void setChanged(boolean changed) {
+		this.changed = changed;
+	}
+
+	public boolean isRandom() {
+		return isRandom;
+	}
+
+	public void setRandom(boolean isRandom) {
+		if (this.isRandom == false)
+			if (log.isDebugEnabled())
+				log.debug("detected random case",this);
+		this.isRandom = isRandom;
+	}
+
+	public void setRemoveGaps(boolean b) {
+		removeGaps = true;
+	}
+	public boolean getRemoveGaps() {
+		return removeGaps;
+	}
+
+	/**
+	 * 
+	 */
+	public void improveSearchResults() {
+		ExtNumbers curr = extNumbersHead;
+		while (curr != null) {
+			ExtNumbers en = curr.splitLargeGaps();
+			if (en != curr) {
+				if (en.hasNumbers() && en.next != null && en.next.hasNumbers())
+					setChanged(true);
+				else {
+					ExtNumbers test = en.hasNumbers() ?  en : en.next;
+					if (test.getNumbers().isSimilar(curr.getNumbers()) == false)
+						setChanged(true);
+				}
+				if (curr.prev == null)
+					extNumbersHead = en;
+				curr = en;
+				continue;
+			}
+			curr = curr.next;
+		}
+	}
+
+	public String toString(){
+		return getRoad().toString() + " " + houseNumbers;
+	}
+
+
+	/**
+	 * Check if street name is set, if not, try to find one. 
+	 * Identify those houses which are assigned to this road because it was the closest,
+	 * but can't be correct because street name doesn't match.
+	 * 
+	 * @param road2HousenumberRoadMap maps {@link MapRoad} instances to corresponding  
+	 * {@link HousenumberRoad} instances
+	 * @param nodeId2RoadLists maps node ids to the {@link MapRoad} that use the corresponding nodes.  
+	 * @return
+	 */
+	public List<HousenumberMatch> checkStreetName(Map<MapRoad, HousenumberRoad> road2HousenumberRoadMap, Int2ObjectOpenHashMap<HashSet<MapRoad>> nodeId2RoadLists) {
+		List<HousenumberMatch> noWrongHouses = Collections.emptyList();
+		List<HousenumberMatch> wrongHouses = Collections.emptyList();
+		double minDist = Double.MAX_VALUE;
+		double maxDist = 0;
+		if (houseNumbers.isEmpty() == false){
+			HashMap<String, Integer>possibleStreetNamesFromHouses = new HashMap<>();
+			HashMap<String, Integer>possiblePlaceNamesFromHouses = new HashMap<>();
+			for (HousenumberMatch house : houseNumbers){
+				if (house.getDistance() > maxDist)
+					maxDist = house.getDistance();
+				if (house.getDistance() < minDist)
+					minDist = house.getDistance();
+				String potentialName = house.getStreet();
+				if (potentialName != null){
+					Integer oldCount = possibleStreetNamesFromHouses.put(potentialName, 1);
+					if (oldCount != null)
+						possibleStreetNamesFromHouses.put(potentialName, oldCount + 1);
+				}
+				String placeName = house.getPlace();
+				if (placeName != null){
+					Integer oldCount = possiblePlaceNamesFromHouses.put(placeName, 1);
+					if (oldCount != null)
+						possiblePlaceNamesFromHouses.put(placeName, oldCount + 1);
+				}
+			}
+			HashSet<String> connectedRoadNames = new HashSet<>();
+			for (Coord co : road.getPoints()){
+				if (co.getId() == 0)
+					continue;
+				HashSet<MapRoad> connectedRoads = nodeId2RoadLists.get(co.getId());
+				for (MapRoad r : connectedRoads){
+					if (r.getStreet() != null)
+						connectedRoadNames.add(r.getStreet());
+				}
+			}
+			if (streetName != null){
+				if (possibleStreetNamesFromHouses.isEmpty()){
+					// ok, houses have no street name 
+					return noWrongHouses; 
+				}
+				if (possibleStreetNamesFromHouses.size() == 1){
+					if (possibleStreetNamesFromHouses.containsKey(streetName)){
+						// ok, houses have same name as street 
+						return noWrongHouses; 
+					}
+				}
+			} 
+			if (possibleStreetNamesFromHouses.isEmpty()){
+				// neither road not houses tell us a street name
+				if (furtherNames != null && furtherNames.size() > 0){
+					Iterator<String> iter = furtherNames.iterator();
+					streetName = iter.next();
+					iter.remove();
+					if (furtherNames.isEmpty())
+						furtherNames = null;
+				}
+				return noWrongHouses;
+			}
+			if (streetName == null){
+				if (possibleStreetNamesFromHouses.size() == 1){
+					String potentialName = possibleStreetNamesFromHouses.keySet().iterator().next();
+					boolean nameOK = false;
+					if (connectedRoadNames.contains(potentialName))
+						nameOK = true;
+					else if (houseNumbers.size() > 1){
+						nameOK = true;
+					} else if (maxDist <= 10){ 
+						nameOK = true;
+					}
+					if (nameOK){
+						streetName = potentialName;
+						return noWrongHouses; // all good, return empty list
+						
+					}
+				} else {
+					List<String> matchingNames = new ArrayList<>();
+					for (Entry<String, Integer> entry : possibleStreetNamesFromHouses.entrySet()){
+						String name = entry.getKey();
+						if (connectedRoadNames.contains(name)){
+							matchingNames.add(name);
+						}
+					}
+					if (matchingNames.size() == 1){
+						streetName = matchingNames.get(0);
+					}
+				}
+
+				
+			}
+			// if we get here we have no usable street name
+			wrongHouses  = new ArrayList<>();
+			Iterator<HousenumberMatch> iter = houseNumbers.iterator();
+			while (iter.hasNext()){
+				HousenumberMatch house = iter.next();
+				if (streetName != null){
+					if (house.getStreet() == null || streetName.equals(house.getStreet()))
+						continue;
+				} else if (house.getPlace() != null)
+					continue;
+				double bestDist = Double.MAX_VALUE;
+				HousenumberMatch best = null;
+				for (MapRoad altRoad : house.getAlternativeRoads()){
+					if (house.getStreet() != null){
+						if (house.getStreet().equals(altRoad.getStreet())){
+							HousenumberMatch test = new HousenumberMatch(house);
+							HousenumberGenerator.findClosestRoadSegment(test, altRoad);
+							if (test.getDistance() < bestDist){
+								best = test;
+								bestDist = test.getDistance();
+							}
+						}
+					}
+				}
+				iter.remove();
+				if (best != null){
+					best.calcRoadSide();
+					wrongHouses.add(best);
+				} else {
+					log.warn("found no plausible road for address",house.getStreet(),house,house.getElement().toBrowseURL());
+				}
+			}
+			
+		}
+		return wrongHouses;
+	}
+
+	public void addHouse(HousenumberMatch house) {
+		if (extNumbersHead != null){
+			log.error("internal error: trying to add house to road that was already processed",this.getRoad(),house);
+		}
+		house.setHousenumberRoad(this);
+		houseNumbers.add(house);
+	}
+
+	public List<HousenumberMatch> getHouses() {
+		return houseNumbers;
+	}
+
+
+	public void setZipCodeInfo(ZipCodeInfo zipInfo) {
+		roadZipCode = zipInfo;
+	}
+}
+
+
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Element.java b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
index 0c36786..5421f3a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Element.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
@@ -22,13 +22,13 @@ import java.util.Map;
 /**
  * Superclass of the node, segment and way OSM elements.
  */
-public abstract class Element {
-	private Tags tags;
-	private long id;
-	private long originalId;
-
-	public int getTagCount() {
-		return (tags == null ? 0 : tags.size());
+public abstract class Element {
+	private Tags tags;
+	private long id;
+	private long originalId;
+
+	public int getTagCount() {
+		return (tags == null ? 0 : tags.size());
 	}
 	
 	/**
@@ -168,25 +168,25 @@ public abstract class Element {
 	}
 	
 	public long getId() {
-		return id;
-	}
-
-	/**
-	 * Returns the Id of the original OSM element on which this element was based.
-	 * <p>
-	 * The Id of the original element will be different from the Id of this
-	 * element if this element uses a faked Id.
-	 */
-	public long getOriginalId() {
-		return originalId;
-	}
-
-	protected void setId(long id) {
-		this.id = id;
-		originalId = id;
-	}
-
-	public void setFakeId() {
+		return id;
+	}
+
+	/**
+	 * Returns the Id of the original OSM element on which this element was based.
+	 * <p>
+	 * The Id of the original element will be different from the Id of this
+	 * element if this element uses a faked Id.
+	 */
+	public long getOriginalId() {
+		return originalId;
+	}
+
+	protected void setId(long id) {
+		this.id = id;
+		originalId = id;
+	}
+
+	public void setFakeId() {
 		id = FakeIdGenerator.makeFakeId();
 	}
 	
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java b/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
index a1b4ca8..6b0ba04 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
@@ -84,13 +84,6 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 			usedTags.add("cycleway:left");
 			usedTags.add("cycleway:right");
 		}
-		
-		// add addr:street and addr:housenumber if housenumber search is enabled
-		if (props.getProperty("housenumbers", false)) {
-			usedTags.add("addr:street");
-			usedTags.add("addr:housenumber");
-		}
-	
 		return true;
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/HousenumberHooks.java b/src/uk/me/parabola/mkgmap/reader/osm/HousenumberHooks.java
new file mode 100644
index 0000000..ca0c9b5
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/reader/osm/HousenumberHooks.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.EnhancedProperties;
+
+/**
+ * Collect data from ways with addr:interpolation tag.
+ * @author GerdP
+ *  
+ */
+public class HousenumberHooks extends OsmReadingHooksAdaptor {
+	private static final Logger log = Logger.getLogger(HousenumberHooks.class);
+	
+	private ElementSaver saver;
+	private Node currentNodeInWay;
+	private final List<Node> nodes = new ArrayList<>();
+	private boolean clearNodes;
+	
+	private static final short addrStreetTagKey = TagDict.getInstance().xlate("addr:street");
+	private static final short addrHousenumberTagKey = TagDict.getInstance().xlate("addr:housenumber");
+	private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation");
+	
+	public static final short partOfInterpolationTagKey = TagDict.getInstance().xlate("mkgmap:part-of-interpolation");
+	public static final short mkgmapNodeIdsTagKey = TagDict.getInstance().xlate("mkgmap:node-ids");
+	@Override
+	public boolean init(ElementSaver saver, EnhancedProperties props) {
+		this.saver = saver;
+		if (props.getProperty("addr-interpolation", true) == false)
+			return false;
+		return (props.getProperty("housenumbers", false));
+	}
+
+	@Override
+	public Set<String> getUsedTags() {
+		HashSet<String> usedTags = new HashSet<>();
+		usedTags.add("addr:street");
+		usedTags.add("addr:housenumber");
+		usedTags.add("addr:interpolation");
+		usedTags.add("addr:place");
+		return usedTags;
+	}
+	
+	@Override
+	public void onCoordAddedToWay(Way way, long id, Coord co) {
+		if (clearNodes){
+			nodes.clear();
+			clearNodes = false;
+		}
+		currentNodeInWay = saver.getNode(id);
+		if (currentNodeInWay == null)
+			return;
+		if (currentNodeInWay.getTag(addrHousenumberTagKey) == null)
+			return;
+		// this node might be part of a way that has the addr:interpolation tag
+		nodes.add(currentNodeInWay);
+	}
+
+	@Override
+	public void onAddWay(Way way) {
+		clearNodes = true; // make sure that the list is cleared with the next coord
+		String ai = way.getTag(addrInterpolationTagKey);
+		if (ai == null)
+			return;
+		if (nodes.size() < 2){
+				log.warn(way.toBrowseURL(),"tag addr:interpolation="+ai, "is ignored, found less than two valid nodes.");
+			return;
+		}
+		switch (ai) {
+		case "odd":
+		case "even":
+		case "all":
+		case "1":
+		case "2":
+			break;
+		default:
+			if (log.isInfoEnabled())
+				log.warn(way.toBrowseURL(),"tag addr:interpolation="+ai, "is ignored");
+			return;
+		}
+		
+		StringBuilder sb = new StringBuilder();
+		int num = nodes.size();
+		for (int i = 0; i < num; i++) {
+			Node n = nodes.get(i);
+			String id = String.valueOf(n.getId());
+			sb.append(id);
+			if (i + 1 < num)
+				sb.append(",");
+			n.addTag(partOfInterpolationTagKey, "1");
+		}
+		way.addTag(mkgmapNodeIdsTagKey, sb.toString());
+	}
+
+	@Override
+	public void end() {
+		nodes.clear();
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java b/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
index ccb0646..4c714fa 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
@@ -21,6 +21,8 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -49,9 +51,12 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	private IdentityHashMap<Coord, Set<Way>> adjacentWays = new IdentityHashMap<Coord, Set<Way>>();
 	/** Contains all _link ways that have to be processed */
 	private Map<Long, Way> destinationLinkWays = new HashMap<Long, Way>();
-
-	private HashSet<String> tagValues = new HashSet<String>(Arrays.asList(
-			"motorway_link", "trunk_link"));
+	
+	private final static Set<String> highwayTypes = new LinkedHashSet<String>(Arrays.asList(
+			"motorway", "trunk", "primary", "motorway_link", "trunk_link", "primary_link"));
+	private HashSet<String> linkTypes = new HashSet<String>(Arrays.asList(
+			"motorway_link", "trunk_link", "primary_link"));
+	
 
 	/** Map way ids to its restriction relations so that the relations can easily be updated when the way is split. */
 	private MultiHashMap<Long, RestrictionRelation> restrictions = new MultiHashMap<>();
@@ -73,7 +78,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	}
 
 	/**
-	 * Fills the internal lists with the 
+	 * Fills the internal lists 
 	 */
 	private void retrieveWays() {
 		// collect all ways tagged with highway 
@@ -83,7 +88,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 				continue;
 			}
 			String highwayTag = w.getTag("highway");
-			if (highwayTag != null) {
+			if (highwayTag != null && highwayTypes.contains(highwayTag)) {
 				// the points of the way are kept so that it is easy to get
 				// the adjacent ways for a given _link way
 				String directedDestination = null;
@@ -113,7 +118,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 
 				// if the way is a link way and has a destination tag
 				// put it the list of ways that have to be processed
-				if (tagValues.contains(highwayTag)) {
+				if (linkTypes.contains(highwayTag)) {
 					String destinationTag = w.getTag("destination");
 					
 					if (destinationTag == null) {
@@ -440,23 +445,6 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	 * mkgmap:exit_hint_ref, mkgmap:exit_hint_name and/or mkgmap:exit_hint_exit_to.
 	 */
 	private void processWays() {
-		// collect all nodes of highway=motorway/trunk ways so that we can check if an exit node
-		// belongs to a motorway/trunk or is a "subexit" within a motorway/trunk junction
-		Set<Coord> motorwayCoords = new HashSet<Coord>();
-		Set<Coord> trunkCoords = new HashSet<Coord>();
-		if (processExits){
-			for (Way w : saver.getWays().values()) {
-				String motorwayTag = w.getTag("highway");
-				if (motorwayTag != null) {
-					if (motorwayTag.equals("motorway"))
-						motorwayCoords.addAll(w.getPoints());
-					else if (motorwayTag.equals("trunk"))
-						trunkCoords.addAll(w.getPoints());
-				}
-			}	
-		}
-		
-		
 		// remove the adjacent links from the destinationLinkWays list
 		// to avoid duplicate dest_hints
 		Queue<Way> linksWithDestination = new ArrayDeque<Way>();
@@ -465,7 +453,6 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 		while (linksWithDestination.isEmpty()== false) {
 			Way linkWay = linksWithDestination.poll();
 			String destination = linkWay.getTag("destination");
-			
 
 			if (log.isDebugEnabled())
 				log.debug("Check way",linkWay.getId(),linkWay.toTagString());
@@ -500,20 +487,39 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 		log.debug(destinationLinkWays.size(),"links with destination tag after cleanup");
 		
 		if (processExits) {
+			// collect all nodes of highway=motorway/trunk ways so that we can check if an exit node
+			// belongs to a motorway/trunk or is a "subexit" within a motorway/trunk junction
+			
+			Map<String, Set<Coord>> highwayCoords = new LinkedHashMap<>();
+			for (String type : highwayTypes){
+				highwayCoords.put(type, new HashSet<Coord>());
+			}
+			for (Way w : saver.getWays().values()) {
+				String highwayTag = w.getTag("highway");
+				if (highwayTag == null)
+					continue;
+				if (highwayTypes.contains(highwayTag)){
+					Set<Coord> set = highwayCoords.get(highwayTag);
+					set.addAll(w.getPoints());
+				}
+			}	
+			
 			// get all nodes tagged with highway=motorway_junction
 			for (Node exitNode : saver.getNodes().values()) {
 				if (isTaggedAsExit(exitNode) && saver.getBoundingBox().contains(exitNode.getLocation())) {
-				
-					boolean isMotorwayExit = motorwayCoords.contains(exitNode.getLocation());
-					boolean isTrunkExit = trunkCoords.contains(exitNode.getLocation());
-					boolean isHighwayExit = isMotorwayExit || isTrunkExit;
-					// use exits only if they are located on a motorway or trunk
-					if (isHighwayExit == false) {
+					String expectedHighwayTag = null;
+					for (Entry<String, Set<Coord>> entry : highwayCoords.entrySet()){
+						if (entry.getValue().contains(exitNode.getLocation())){
+							expectedHighwayTag = entry.getKey();
+							break;
+						}
+					}
+					if (expectedHighwayTag == null){
+						// use exits only if they are located on a motorway or trunk
 						if (log.isDebugEnabled())
 							log.debug("Skip non highway exit:", exitNode.toBrowseURL(), exitNode.toTagString());
 						continue;
 					}
-				
 					// retrieve all ways with this exit node
 					Set<Way> exitWays = adjacentWays.get(exitNode.getLocation());
 					if (exitWays==null) {
@@ -525,13 +531,18 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 					// the inserted node has the correct orientation 
 					List<Entry<Coord, Way>> nextNodes = getNextNodes(exitNode.getLocation(), true);
 					Coord nextHighwayNode = null;
-					String expectedHighwayTag = (isMotorwayExit ? "motorway" : "trunk");
+					int countMatches = 0;
 					for (Entry<Coord, Way> nextNode : nextNodes) {
 						if (expectedHighwayTag.equals(nextNode.getValue().getTag("highway"))) {
 							nextHighwayNode = nextNode.getKey();
-							break;
+							countMatches++;
 						} 	
 					}
+					if (countMatches > 1){
+						// may happen when the highway is a link which splits further into two or more links
+						// ignore the node 
+						nextHighwayNode = null;
+					}
 				
 					// use link ways only
 					for (Way w : exitWays) {
@@ -548,7 +559,6 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 						String highwayLinkTag = w.getTag("highway");
 						if (highwayLinkTag.endsWith("_link")) {
 							log.debug("Try to cut",highwayLinkTag, w, "into three parts for giving hint to exit", exitNode);
-
 							// calc the way length to decide how to cut the way
 							double wayLength = getLength(w);
 							if (wayLength < 10 && w.getPoints().size() < 3) {
@@ -587,10 +597,14 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 								}
 								if (exitNode.getTag("ref") != null)
 									hintWay.addTag("mkgmap:exit_hint_ref", exitNode.getTag("ref"));
-								if (exitNode.getTag("exit_to") != null)
-									hintWay.addTag("mkgmap:exit_hint_exit_to", exitNode.getTag("exit_to"));
-								if (getName(exitNode) != null)
+								if (countMatches == 1){
+									if (exitNode.getTag("exit_to") != null){
+										hintWay.addTag("mkgmap:exit_hint_exit_to", exitNode.getTag("exit_to"));
+									}
+								}
+								if (getName(exitNode) != null){
 									hintWay.addTag("mkgmap:exit_hint_name", getName(exitNode));
+								}
 								
 								if (log.isInfoEnabled())
 									log.info("Cut off exit hint way", hintWay, hintWay.toTagString());
@@ -706,7 +720,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 		adjacentWays = null;
 		wayNodes = null;
 		destinationLinkWays = null;
-		tagValues = null;
+		linkTypes = null;
 		saver = null;
 		nameTags = null;
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
index ddc29b7..5bbf771 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
@@ -14,25 +14,25 @@
  * Author: Steve Ratcliffe
  * Create date: 22-Sep-2007
  */
-package uk.me.parabola.mkgmap.reader.osm;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import uk.me.parabola.imgfmt.ExitException;
-import uk.me.parabola.imgfmt.FormatException;
-import uk.me.parabola.imgfmt.Utils;
-import uk.me.parabola.log.Logger;
+import java.util.Map;
+import java.util.Set;
+
+import uk.me.parabola.imgfmt.ExitException;
+import uk.me.parabola.imgfmt.FormatException;
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
 import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
@@ -62,6 +62,7 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 			new HighwayHooks(),
 			new LocationHook(),
 			new POIGeneratorHook(),
+			new HousenumberHooks(),
 	};
 	protected OsmConverter converter;
 	private final Set<String> usedTags = new HashSet<String>();
@@ -134,34 +135,34 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	 * include a fixed set of strings on the assumption that .osm files
 	 * are probably going to have the OSM copyright statements.
 	 *
-	 * @return A list of copyright messages as a String array.
-	 */
-	public String[] copyrightMessages() {
-		String copyrightFileName = getConfig().getProperty("copyright-file", null);
-		if (copyrightFileName != null)
-		{
-			File file = new File(copyrightFileName);
-			List<String> copyrightArray = new ArrayList<String>();
-			try {
-				BufferedReader reader = new BufferedReader(new FileReader(file));
-				String text;
-				while ((text = reader.readLine()) != null) {
-					copyrightArray.add(text);
-				}
-
-				reader.close();
-			} catch (FileNotFoundException e) {
-				throw new ExitException("Could not open copyright file " + copyrightFileName);
-			} catch (IOException e) {
-				throw new ExitException("Error reading copyright file " + copyrightFileName);
-			}
-			String[] copyright = new String[copyrightArray.size()];
-			copyrightArray.toArray(copyright);
-			return copyright;
-		}
-		String note = getConfig().getProperty("copyright-message", 
-				"OpenStreetMap.org contributors. See: http://wiki.openstreetmap.org/index.php/Attribution");
-		return new String[] { note };
+	 * @return A list of copyright messages as a String array.
+	 */
+	public String[] copyrightMessages() {
+		String copyrightFileName = getConfig().getProperty("copyright-file", null);
+		if (copyrightFileName != null)
+		{
+			File file = new File(copyrightFileName);
+			List<String> copyrightArray = new ArrayList<String>();
+			try {
+				BufferedReader reader = new BufferedReader(new FileReader(file));
+				String text;
+				while ((text = reader.readLine()) != null) {
+					copyrightArray.add(text);
+				}
+
+				reader.close();
+			} catch (FileNotFoundException e) {
+				throw new ExitException("Could not open copyright file " + copyrightFileName);
+			} catch (IOException e) {
+				throw new ExitException("Error reading copyright file " + copyrightFileName);
+			}
+			String[] copyright = new String[copyrightArray.size()];
+			copyrightArray.toArray(copyright);
+			return copyright;
+		}
+		String note = getConfig().getProperty("copyright-message", 
+				"OpenStreetMap.org contributors. See: http://wiki.openstreetmap.org/index.php/Attribution");
+		return new String[] { note };
 	}
 
 	protected void setStyle(Style style) {
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java b/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
index 706e826..d427492 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
@@ -366,29 +366,47 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 			if (r instanceof MultiPolygonRelation == false) {
 				continue;
 			}
-			// boundary relations may have a node with role admin_centre, if yes, use the 
-			// location of it
-			Node existingPOI = null;
-			if ("boundary".equals(r.getTag("type")) && "administrative".equals(r.getTag("boundary"))){
+			Node admin_centre = null;
+			Node labelPOI = null;
+			String relName = getName(r);
+			if (relName != null){
 				for (Entry<String, Element> pair : r.getElements()){
 					String role = pair.getKey();
-					Element el = pair.getValue(); 
-					if ("admin_centre".equals(role)){
-						if (el instanceof Node){
-							String bName = getName(r);
-							String pName = getName(el);
-							if (bName != null && bName.equals(pName))
-								existingPOI = (Node) el;
+					Element el = pair.getValue();
+					if (el instanceof Node){
+						if ("admin_centre".equals(role)){
+							if ("boundary".equals(r.getTag("type")) && "administrative".equals(r.getTag("boundary"))){
+								// boundary relations may have a node with role admin_centre, if yes, use the 
+								// location of it
+								String pName = getName(el);
+								if (relName.equals(pName)){
+									admin_centre = (Node) el;
+									if (log.isDebugEnabled())
+										log.debug("using admin_centre node as location for POI for rel",r.getId(),relName,"at",((Node) el).getLocation().toDegreeString());
+								}
+							}
+						} else if ("label".equals(role)){
+							String label = getName(el);
+							if (relName.equals(label)){
+								labelPOI = (Node) el;
+								log.debug("using label node as location for POI for rel",r.getId(),relName,"at",((Node) el).getLocation().toDegreeString());
+								break;
+							} else {
+								log.warn("rel",r.toBrowseURL(),",node with role label is ignored because it has a different name");
+							}
 						}
-						break;
 					}
 				}
 			}
-			Coord point;
-			if (existingPOI ==null)
+			Coord point = null;
+			if (admin_centre == null && labelPOI == null)
 				point = ((MultiPolygonRelation)r).getCofG();
-			else 
-				point = existingPOI.getLocation();
+			else {
+				if (labelPOI != null)
+					point = labelPOI.getLocation();
+				else 
+					point = admin_centre.getLocation();
+			}
 			if (point == null) {
 				continue;
 			}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Rule.java b/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
index 0a0dd62..dba5680 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
@@ -54,5 +54,7 @@ public interface Rule {
 	public void setFinalizeRule(Rule finalizeRule);
 	
 	public void printStats(String header);
+
+	public Rule getFinalizeRule();
 	
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Way.java b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
index 8ad9aa4..c78d458 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Way.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
@@ -193,7 +193,10 @@ public class Way extends Element {
 
 		double lat = 0;
 		double lon = 0;
-		for(Coord p : points) {
+		if (hasIdenticalEndPoints())
+			numPoints--;
+		for (int i = 0; i < numPoints; i++){
+			Coord p = points.get(i);
 			lat += (double)p.getHighPrecLat()/numPoints;
 			lon += (double)p.getHighPrecLon()/numPoints;
 		}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
index 72c43cf..efba2ac 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
@@ -67,13 +67,13 @@ public class OsmBinHandler extends OsmHandler {
 
 				int tagCount = binNode.getKeysCount();
 				if (tagCount > 0) {
-					Node node = new Node(id, co);
-					for (int tid = 0; tid < tagCount; tid++) {
-						String key = getStringById(binNode.getKeys(tid));
-						String val = getStringById(binNode.getVals(tid)).trim();
-						key = keepTag(key, val);
-						if (key != null)
-							node.addTag(key, val.intern());
+					Node node = new Node(id, co);
+					for (int tid = 0; tid < tagCount; tid++) {
+						String key = getStringById(binNode.getKeys(tid));
+						String val = getStringById(binNode.getVals(tid)).trim();
+						key = keepTag(key, val);
+						if (key != null)
+							node.addTag(key, val.intern());
 					}
 
 					saver.addNode(node);
@@ -102,13 +102,13 @@ public class OsmBinHandler extends OsmHandler {
 					int ntags = 0;
 					Node node = null;
 					while (nodes.getKeysVals(kvid) != 0) {
-						int keyid = nodes.getKeysVals(kvid++);
-						int valid = nodes.getKeysVals(kvid++);
-						String key = getStringById(keyid);
-						String val = getStringById(valid).trim();
-						key = keepTag(key, val);
-						if (key != null) {
-							if (node == null)
+						int keyid = nodes.getKeysVals(kvid++);
+						int valid = nodes.getKeysVals(kvid++);
+						String key = getStringById(keyid);
+						String val = getStringById(valid).trim();
+						key = keepTag(key, val);
+						if (key != null) {
+							if (node == null)
 								node = new Node(id, co);
 							node.addTag(key, val.intern());
 							ntags++;
@@ -129,13 +129,13 @@ public class OsmBinHandler extends OsmHandler {
 			for (Osmformat.Way binWay : ways) {
 				Way way = startWay(binWay.getId());
 
-				for (int j = 0; j < binWay.getKeysCount(); j++) {
-
-					String key = getStringById(binWay.getKeys(j));
-					String val = getStringById(binWay.getVals(j)).trim();
-					key = keepTag(key, val);
-					if (key != null)
-						way.addTag(key, val.intern());
+				for (int j = 0; j < binWay.getKeysCount(); j++) {
+
+					String key = getStringById(binWay.getKeys(j));
+					String val = getStringById(binWay.getVals(j)).trim();
+					key = keepTag(key, val);
+					if (key != null)
+						way.addTag(key, val.intern());
 				}
 
 				long nid = 0;
@@ -154,13 +154,13 @@ public class OsmBinHandler extends OsmHandler {
 				long id = binRel.getId();
 				GeneralRelation rel = new GeneralRelation(id);
 
-				boolean tagsIncomplete = false;
-				for (int j = 0; j < binRel.getKeysCount(); j++) {
-					String key = getStringById(binRel.getKeys(j));
-					String val = getStringById(binRel.getVals(j)).trim();
-					// type is required for relations - all other tags are filtered
-					if ("type".equals(key))
-						// intern the string
+				boolean tagsIncomplete = false;
+				for (int j = 0; j < binRel.getKeysCount(); j++) {
+					String key = getStringById(binRel.getKeys(j));
+					String val = getStringById(binRel.getVals(j)).trim();
+					// type is required for relations - all other tags are filtered
+					if ("type".equals(key))
+						// intern the string
 						key = "type";
 					else
 						key = keepTag(key, val);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
index 26d06ed..1992725 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
@@ -311,13 +311,13 @@ public class O5mBinHandler extends OsmHandler{
 	
 	private boolean readTags(Element elem) throws IOException{
 		boolean tagsIncomplete = false;
-		while (bytesToRead > 0){
-			readStringPair();
-			String key = stringPair[0];
-			String val = stringPair[1].trim();
-			// the type tag is required for relations - all other tags are filtered
-			if (elem instanceof Relation && "type".equals(key))
-				// intern the string
+		while (bytesToRead > 0){
+			readStringPair();
+			String key = stringPair[0];
+			String val = stringPair[1].trim();
+			// the type tag is required for relations - all other tags are filtered
+			if (elem instanceof Relation && "type".equals(key))
+				// intern the string
 				key = "type";
 			else
 				key = keepTag(key, val);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
index 730a20d..8af21ec 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
@@ -204,13 +204,13 @@ public class Osm5XmlHandler extends OsmHandler {
 	 * @param qName The new tag name.
 	 * @param attributes Its attributes.
 	 */
-	private void startInNode(String qName, Attributes attributes) {
-		if (qName.equals("tag")) {
-			String key = attributes.getValue("k");
-			String val = attributes.getValue("v").trim();
-
-			// We only want to create a full node for nodes that are POI's
-			// and not just one point of a way.  Only create if it has tags that
+	private void startInNode(String qName, Attributes attributes) {
+		if (qName.equals("tag")) {
+			String key = attributes.getValue("k");
+			String val = attributes.getValue("v").trim();
+
+			// We only want to create a full node for nodes that are POI's
+			// and not just one point of a way.  Only create if it has tags that
 			// could be used in a POI.
 			key = keepTag(key, val);
 			if (key != null) {
@@ -232,13 +232,13 @@ public class Osm5XmlHandler extends OsmHandler {
 	private void startInWay(String qName, Attributes attributes) {
 		if (qName.equals("nd")) {
 			long id = idVal(attributes.getValue("ref"));
-			addCoordToWay(currentWay, id);
-		} else if (qName.equals("tag")) {
-			String key = attributes.getValue("k");
-			String val = attributes.getValue("v").trim();
-			key = keepTag(key, val);
-			if (key != null)
-				currentWay.addTag(key, val.intern());
+			addCoordToWay(currentWay, id);
+		} else if (qName.equals("tag")) {
+			String key = attributes.getValue("k");
+			String val = attributes.getValue("v").trim();
+			key = keepTag(key, val);
+			if (key != null)
+				currentWay.addTag(key, val.intern());
 		}
 	}
 
@@ -273,13 +273,13 @@ public class Osm5XmlHandler extends OsmHandler {
 			} else
 				el = null;
 			if (el != null) // ignore non existing ways caused by splitting files
-				currentRelation.addElement(attributes.getValue("role"), el);
-		} else if (qName.equals("tag")) {
-			String key = attributes.getValue("k");
-			String val = attributes.getValue("v").trim();
-			// the type tag is required for relations - all other tags are filtered
-			if ("type".equals(key))
-				// intern the key
+				currentRelation.addElement(attributes.getValue("role"), el);
+		} else if (qName.equals("tag")) {
+			String key = attributes.getValue("k");
+			String val = attributes.getValue("v").trim();
+			// the type tag is required for relations - all other tags are filtered
+			if ("type".equals(key))
+				// intern the key
 				key = "type";
 			else
 				key = keepTag(key, val);
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java b/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
index 4ee46d1..be18648 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
@@ -19,7 +19,6 @@ package uk.me.parabola.mkgmap.reader.polish;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 
 import uk.me.parabola.imgfmt.app.Coord;
@@ -45,9 +44,6 @@ class RoadHelper {
 	// routing node store, persistent over resets
 	private final Map<Long, CoordNode> nodeCoords = new HashMap<>();
 
-	// Next node number to use for nodes constructed for house numbers. Persists over reset.
-	private long houseNumberNodeNumber = 16000000;
-
 	private int roadId;
 	private final List<NodeIndex> nodes = new ArrayList<>();
 
@@ -127,7 +123,7 @@ class RoadHelper {
 		if (log.isDebugEnabled())
 			log.debug("finishing road id " + roadId);
 
-		MapRoad road = new MapRoad(roadId, l);
+		MapRoad road = new MapRoad(roadId, roadId, l);
 
 		// Set parameters.
 		road.setRoadClass(roadClass);
@@ -139,21 +135,14 @@ class RoadHelper {
 		road.setAccess(mkgmapAccess);
 
 		if (numbers != null && !numbers.isEmpty()) {
-			convertNodesForHouseNumbers();
+			convertNodesForHouseNumbers(road);
 			road.setNumbers(numbers);
 		}
 
 		List<Coord> points = road.getPoints();
-		road.setNumNodes(nodes.size());
 
-		boolean starts = false;
-		boolean intern = false;
 		for (NodeIndex ni : nodes) {
 			int n = ni.index;
-			if (n == 0)
-				starts = true;
-			else if (n < points.size() - 1)
-				intern = true;
 			if (log.isDebugEnabled())
 				log.debug("road has " + points.size() +" points");
 			Coord coord = points.get(n);
@@ -169,75 +158,23 @@ class RoadHelper {
 				log.warn("Inconsistant node ids");
 			}
 		}
-		road.setStartsWithNode(starts);
-		road.setInternalNodes(intern);
 
 		return road;
 	}
 
 	/**
-	 * Convert the node index into a routing node number.
-	 *
-	 * If necessary a new routing node is created, if there is not one already
-	 * These constructed routing nodes are not connected to any other road and so
-	 * should be marked as such in the NOD2 bit stream, but we don't appear to do that yet.
-	 *
+	 * Make sure that each node that is referenced by the house
+	 * numbers is a number node. Some of them will later be changed
+	 * to routing nodes.
 	 * Only called if numbers is non-null and not empty.
 	 */
-	private void convertNodesForHouseNumbers() {
+	private void convertNodesForHouseNumbers(MapRoad road) {
+		int rNodNumber = 0;
 		for (Numbers n : numbers) {
 			int node = n.getNodeNumber();
-
-			// This assumes that the nodes are sorted by index.
-			ListIterator<NodeIndex> iterator = nodes.listIterator();
-			while (iterator.hasNext()) {
-				NodeIndex ni = iterator.next();
-				if (ni.index == node) {
-					// It was already there (a common case)
-					n.setRnodNumber(iterator.previousIndex());
-					break;
-				} else if (ni.index > node) {
-					// there is no routing node for this node index, need to insert one.
-					break;
-				}
-			}
-
-			// If we don't have a routing node number then we have to construct one.
-			if (!n.hasRnodNumber()) {
-				NodeIndex hnNode = new NodeIndex(new String[] {
-						String.valueOf(node),
-						String.valueOf(houseNumberNodeNumber++),
-						"0"
-				});
-
-				iterator.previous();
-				iterator.add(hnNode);
-				n.setRnodNumber(iterator.previousIndex());
-				//System.out.printf("ADDING RN on %d, hn=%s, rn=%d\n", roadId, hnNode, n.getRnodNumber());
-			}
+			n.setIndex(rNodNumber++);
+			road.getPoints().get(node).setNumberNode(true);
 		}
-
-		// Sanity checking. TODO remove
-		//int lastInd = -1;
-		//for (NodeIndex n : nodes) {
-		//	assert n.index > lastInd;
-		//	lastInd = n.index;
-		//
-		//}
-		//System.out.println("start");
-		//Numbers num = null;
-		//for (Numbers n1 : numbers) {
-		//	int ncount = 0;
-		//	for (NodeIndex n : nodes) {
-		//		System.out.printf("n1.node=%d, ni=%s, ni.index=%d\n", n1.getNodeNumber(), n, n.index);
-		//		if (n1.getNodeNumber() == n.index) {
-		//			num = n1;
-		//			break;
-		//		}
-		//		ncount++;
-		//	}
-		//	assert num != null && num.getRnodNumber() == ncount;
-		//}
 	}
 
 	public boolean isRoad() {
diff --git a/src/uk/me/parabola/mkgmap/reader/test/AllElements.java b/src/uk/me/parabola/mkgmap/reader/test/AllElements.java
index fc3a609..79631ea 100644
--- a/src/uk/me/parabola/mkgmap/reader/test/AllElements.java
+++ b/src/uk/me/parabola/mkgmap/reader/test/AllElements.java
@@ -18,12 +18,14 @@ package uk.me.parabola.mkgmap.reader.test;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Properties;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.general.MapCollector;
 import uk.me.parabola.mkgmap.general.MapLine;
 import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapShape;
+import uk.me.parabola.mkgmap.reader.osm.GType;
 
 
 /**
@@ -51,16 +53,21 @@ class AllElements {
 	private static final double ELEMENT_SPACING = 0.002;
 	private static final double ELEMENT_SIZE = 0.001;
 
-	// I don't know what the max types and subtypes actually are, adjust if
+	// I don't know what the max types and sub-types actually are, adjust if
 	// there seems to be more beyond.
 	private static final int MAX_POINT_TYPE = 0x7f;
-	private static final int MAX_POINT_SUB_TYPE = 0x30;
+	private static final int MAX_POINT_SUB_TYPE = 0x1f;
 
 	// we draw lines and polygons in a 16x16 square (or whatever is here).
-	private static final int MAX_LINE_TYPE_X = 8;
-	private static final int MAX_LINE_TYPE_Y = 8;
-	private static final int MAX_SHAPE_TYPE_X = 12;
-	private static final int MAX_SHAPE_TYPE_Y = 12;
+	private static final int MAX_LINE_TYPE_X = 4;
+	private static final int MAX_LINE_TYPE_Y = 16;
+	private static final int MAX_SHAPE_TYPE_X = 8;
+	private static final int MAX_SHAPE_TYPE_Y = 16;
+	private Properties configProps;
+
+	public AllElements(Properties configProps) {
+		this.configProps = configProps;
+	}
 
 	/**
 	 * Loading the map in this case means generating it.
@@ -69,17 +76,21 @@ class AllElements {
 	 */
 	public void load(MapCollector mapper) {
 		double baseLat = 51.7;
+		double baseLong = 0.24;
 
 		String sBaseLat = System.getenv("BASE_LAT");
 		String sBaseLong = System.getenv("BASE_LONG");
+		if (sBaseLat == null)
+			sBaseLat = configProps.getProperty("base-lat");
+		if (sBaseLong == null)
+			sBaseLong = configProps.getProperty("base-long");
 
 		if (sBaseLat != null)
 			baseLat = Double.valueOf(sBaseLat);
 
-		double baseLong = 0.24;
 		if (sBaseLong != null)
 			baseLong = Double.valueOf(sBaseLong);
-
+		
 		drawTestMap(mapper, baseLat, baseLong);
 	}
 
@@ -97,7 +108,7 @@ class AllElements {
 
 		drawPoints(map, startLat, lng);
 
-		lng += MAX_POINT_TYPE * ELEMENT_SPACING;
+		lng += (MAX_POINT_TYPE + 1) * ELEMENT_SPACING;
 		drawLines(map, startLat, lng);
 
 		lng += MAX_LINE_TYPE_X * ELEMENT_SPACING;
@@ -108,58 +119,59 @@ class AllElements {
 
 		double lat = slat + 0.004;
 		double lon = slon + 0.002;
-
-		for (int type = 0; type < MAX_POINT_TYPE; type++) {
-			for (int subtype = 0; subtype < MAX_POINT_SUB_TYPE; subtype++) {
-
+		
+		for (int maintype = 0; maintype <= MAX_POINT_TYPE; maintype++) {
+			for (int subtype = 0; subtype <= MAX_POINT_SUB_TYPE; subtype++) {
+				int type = (maintype << 8) + subtype;
 				MapPoint point = new MapPoint();
 
 				double baseLat = lat + subtype * ELEMENT_SPACING;
-				double baseLong = lon + type * ELEMENT_SPACING;
+				double baseLong = lon + maintype * ELEMENT_SPACING;
 
 				point.setMinResolution(10);
-				point.setName("0x" + Integer.toHexString(type)
-								+ ','
-								+ "0x" + Integer.toHexString(subtype));
-
+				point.setName(GType.formatType(type));
 				point.setLocation(new Coord(baseLat, baseLong));
-				point.setType((type << 8) + subtype);
+				point.setType(type);
 
 				mapper.addPoint(point);
+				
+				if (configProps.containsKey("verbose"))
+					System.out.println("Generated POI " + GType.formatType(type) + " at " + point.getLocation().toDegreeString()); 
 				mapper.addToBounds(point.getLocation()); // XXX shouldn't be needed.
 			}
 		}
 	}
 
 	private void drawLines(MapCollector mapper, double slat, double slon) {
-
+		
 		double lat = slat + 0.004;
 		double lon = slon + 0.002;
-
+		int type = 0;
 		for (int x = 0; x < MAX_LINE_TYPE_X; x++) {
 			for (int y = 0; y < MAX_LINE_TYPE_Y; y++) {
-				int type = x*MAX_LINE_TYPE_X + y;
-				if ((type & 0xc0) != 0)
+				type++;
+				if (type >= 0x40)
 					break;
 
 				MapLine line = new MapLine();
 				line.setMinResolution(10);
-				line.setName("0x" + Integer.toHexString(type));
+				line.setName(GType.formatType(type));
 
 				double baseLat = lat + y * ELEMENT_SPACING;
 				double baseLong = lon + x * ELEMENT_SPACING;
-
 				List<Coord> coords = new ArrayList<Coord>();
 
 				Coord co = new Coord(baseLat, baseLong);
 				coords.add(co);
 				mapper.addToBounds(co);
+				if (configProps.containsKey("verbose"))
+					System.out.println("Generated line " + GType.formatType(type) + " at " + co.toDegreeString());
 
 				co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE);
 				coords.add(co);
 				mapper.addToBounds(co);
 
-				co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE + ELEMENT_SIZE/2);
+				co = new Coord(baseLat + ELEMENT_SIZE, baseLong +	 ELEMENT_SIZE + ELEMENT_SIZE/2);
 				coords.add(co);
 				mapper.addToBounds(co);
 
@@ -176,28 +188,30 @@ class AllElements {
 
 		double lat = slat + 0.004;
 		double lon = slon + 0.002;
-
+		int type = 0;
 		for (int x = 0; x < MAX_SHAPE_TYPE_X; x++) {
 			for (int y = 0; y < MAX_SHAPE_TYPE_Y; y++) {
-				int type = x*16 + y;
-				if ((type & 0x80) != 0)
+				type++;
+				if (type >= 0x80)
 					break;
 
 				//Polygon pg = div.createPolygon("0x" + Integer.toHexString(type));
 
 				MapShape shape = new MapShape();
 				shape.setMinResolution(10);
-				shape.setName("0x" + Integer.toHexString(type));
+				shape.setName(GType.formatType(type));
 
 				double baseLat = lat + y * ELEMENT_SPACING;
 				double baseLong = lon + x * ELEMENT_SPACING;
-
+				
 				List<Coord> coords = new ArrayList<Coord>();
 
 				Coord co = new Coord(baseLat, baseLong);
 				//pg.addCoord(co);
 				coords.add(co);
-
+				if (configProps.containsKey("verbose"))
+					System.out.println("Generated polygon " + GType.formatType(type) + " at " + co.toDegreeString());
+				
 				co = new Coord(baseLat + ELEMENT_SIZE, baseLong);
 				coords.add(co);
 				mapper.addToBounds(co);
@@ -210,9 +224,7 @@ class AllElements {
 				coords.add(co);
 				mapper.addToBounds(co);
 
-				co = new Coord(baseLat, baseLong);
-				coords.add(co);
-				mapper.addToBounds(co);
+				coords.add(coords.get(0));
 
 				shape.setType(type);
 				shape.setPoints(coords);
diff --git a/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java b/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java
index 63e5cab..7b741de 100644
--- a/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java
@@ -49,7 +49,7 @@ public class ElementTestDataSource extends MapperBasedMapDataSource implements L
 	 */
 	public void load(String name) throws FileNotFoundException {
 		if ("test-map:all-elements".equals(name)) {
-			AllElements all = new AllElements();
+			AllElements all = new AllElements(configProps);
 			all.load(mapper);
 		} else if ("test-map:test-points".equals(name)) {
 			TestPoints test = new TestPoints();
diff --git a/src/uk/me/parabola/mkgmap/general/MapPointKdTree.java b/src/uk/me/parabola/util/KdTree.java
similarity index 73%
rename from src/uk/me/parabola/mkgmap/general/MapPointKdTree.java
rename to src/uk/me/parabola/util/KdTree.java
index bd96cd3..d843298 100644
--- a/src/uk/me/parabola/mkgmap/general/MapPointKdTree.java
+++ b/src/uk/me/parabola/util/KdTree.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012.
+ * Copyright (C) 2014 Gerd Petermann
  *
  * 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
@@ -10,46 +10,48 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  */
-
-package uk.me.parabola.mkgmap.general;
+package uk.me.parabola.util;
 
 
+import java.util.LinkedHashSet;
+import java.util.Set;
 import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.mkgmap.general.MapPoint;
 
 
 /**
  * A kd-tree (2D) implementation to solve the nearest neighbor problem.
  * The tree is not explicitly balanced.
  * 
- * @author GerdP
+ * @author Gerd Petermann
  *
  */
-public class MapPointKdTree {
+public class KdTree <T extends Locatable> {
 	private static final boolean ROOT_NODE_USES_LONGITUDE = false;
 	
-	private static class KdNode {
-		MapPoint point;
+	private class KdNode {
+		T point;
 		KdNode left;
 		KdNode right;
 
-		KdNode(MapPoint p) {
+		KdNode(T p) {
 			point = p;
 		}
 	}
 	// the tree root
     private KdNode root;
-    // number of saved MapPoint objects  
+    // number of saved objects  
     private int size;
 
     // helpers 
-    private MapPoint nextPoint ;
+    private T nextPoint ;
     private double minDist;
+    private double maxDist;
+    private Set<T> set;
 
     /**
      *  create an empty tree
      */
-	public MapPointKdTree() {
+	public KdTree() {
 		root = null;
 	}
 
@@ -63,7 +65,7 @@ public class MapPointKdTree {
 	 * Start the add action with the root
 	 * @param toAdd
 	 */
-	public void add(MapPoint toAdd) {
+	public void add(T toAdd) {
 		size++;
 		root = add(toAdd, root, ROOT_NODE_USES_LONGITUDE);
 	}
@@ -93,7 +95,7 @@ public class MapPointKdTree {
 	 * 		<code>false</code> the tree node uses latitude for comparison
 	 * @return the subtree root node after insertion
 	 */
-    private KdNode add( MapPoint toAdd, KdNode tree,  boolean useLongitude){
+    private KdNode add( T toAdd, KdNode tree,  boolean useLongitude){
         if( tree == null ) {
             tree = new KdNode( toAdd );
         } else {
@@ -111,25 +113,49 @@ public class MapPointKdTree {
 	 * @param p the point to search for
 	 * @return the point with shortest distance to <var>p</var>
 	 */
-	public MapPoint findNextPoint(MapPoint p) {
+	public T findNextPoint(Locatable p) {
 		// reset 
 		minDist = Double.MAX_VALUE;
+		maxDist = -1;
+		set = null;
 		nextPoint = null;
 		
 		// false => first node is a latitude level
 		return findNextPoint(p, root, ROOT_NODE_USES_LONGITUDE);
 	}
 
-	private MapPoint findNextPoint(MapPoint p, KdNode tree, boolean useLongitude) {
+	/**
+	 * Searches for the point that has smallest distance to the given point.
+	 * @param p the point to search for
+	 * @return the point with shortest distance to <var>p</var>
+	 */
+	public Set<T> findNextPoint(Locatable p, double maxDist) {
+		// reset 
+		minDist = Double.MAX_VALUE;
+		this.maxDist = Math.pow(maxDist * 360 / Coord.U, 2); // convert maxDist in meter to distanceInDegreesSquared
+		nextPoint = null;
+		this.set = new LinkedHashSet<>();
+		// false => first node is a latitude level
+		findNextPoint(p, root, ROOT_NODE_USES_LONGITUDE);
+		return set;
+	}
+
+	private T findNextPoint(Locatable p, KdNode tree, boolean useLongitude) {
 		boolean continueWithLeft = false;
 		if (tree == null)
 			return nextPoint;
 		
 		if (tree.left == null && tree.right == null){
 			double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
+			if (dist <= maxDist && set != null){
+				set.add(tree.point);
+			}
 			if (dist < minDist){
 				nextPoint = tree.point;
-				minDist = dist;
+				if (dist < maxDist)
+					minDist = maxDist;
+				else
+					minDist = dist;
 			}
 			return nextPoint;
 		}
@@ -145,11 +171,16 @@ public class MapPointKdTree {
 		}
 		
 		double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
+		if (dist <= maxDist && set != null)
+			set.add(tree.point);
 		if (dist < minDist){
 			nextPoint = tree.point;
 			minDist = dist;
-		}
-		
+			if (dist < maxDist)
+				minDist = maxDist;
+			else
+				minDist = dist;
+		} 		
 		// do we have to search the other part of the tree?
 		Coord test;
 		if (useLongitude)
diff --git a/src/uk/me/parabola/util/Locatable.java b/src/uk/me/parabola/util/Locatable.java
new file mode 100644
index 0000000..49f1747
--- /dev/null
+++ b/src/uk/me/parabola/util/Locatable.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+package uk.me.parabola.util;
+
+import uk.me.parabola.imgfmt.app.Coord;
+
+/**
+ * For objects that have a location which can be represented as a Coord instance.
+ * 
+ * @author Gerd Petermann
+ */
+public interface Locatable {
+
+	/**
+	 * get the location of the object
+	 */
+	public Coord getLocation();
+}
diff --git a/src/uk/me/parabola/util/MultiHashSet.java b/src/uk/me/parabola/util/MultiHashSet.java
new file mode 100644
index 0000000..c7e5d41
--- /dev/null
+++ b/src/uk/me/parabola/util/MultiHashSet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Gerd Petermann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+package uk.me.parabola.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+
+public class MultiHashSet<K,V> extends HashMap<K,Set<V>> {
+
+	/**
+	* the empty set to be returned when there is key without values.
+	*/
+	private final Set<V> emptyList = Collections.emptySet();
+
+	/**
+	* Returns the list of values associated with the given key.
+	*
+	* @param key the key to get the values for.
+	* @return a list of values for the given keys or the empty list of no such
+	*         value exist.
+	*/
+	public Set<V> get(Object key) {
+		Set<V> result = super.get(key);
+		return result == null ? emptyList : result;
+	}
+
+
+	public boolean add(K key, V value ) {
+		Set<V> values = super.get(key);
+	    if (values == null ) {
+	        values = new LinkedHashSet<V>();
+	        super.put( key, values );
+	    }
+	    return values.add(value);
+	}
+
+	public boolean removeMapping(K key, V value) {
+	    Set<V> values = super.get(key);
+	    if (values == null )
+			return false;
+
+	    boolean existed = values.remove(value);
+		
+		if (values.isEmpty())
+			super.remove(key);
+
+		return existed;
+	}
+}
+
diff --git a/src/uk/me/parabola/util/ShapeSplitter.java b/src/uk/me/parabola/util/ShapeSplitter.java
index 1f2bc6e..fa882ee 100644
--- a/src/uk/me/parabola/util/ShapeSplitter.java
+++ b/src/uk/me/parabola/util/ShapeSplitter.java
@@ -1,3 +1,15 @@
+/*
+ * Copyright (C) 2014 Gerd Petermann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
 package uk.me.parabola.util;
 
 import java.awt.Shape;
diff --git a/test/func/lib/NumberReader.java b/test/func/lib/NumberReader.java
index f98d1b8..a091613 100644
--- a/test/func/lib/NumberReader.java
+++ b/test/func/lib/NumberReader.java
@@ -100,8 +100,13 @@ public class NumberReader {
 		// To do this properly we need to know the number of nodes I think, this is the
 		// best we can do: if there are more than 8 bits left, there must be another command
 		// left.  We could leave a short command at the end.
-		while (br.getBitPosition() < br.getNumberOfBits() && numbers.size() < numberOfNodes) {
+		while (nodeCounter < numberOfNodes/* + 1*/) {
+			try {
 			runCommand(numbers);
+			} catch (NumberException | ArrayIndexOutOfBoundsException e) {
+				System.out.printf("collected %d, wanted %d\n", numbers.size(), numberOfNodes+1);
+		return numbers;
+	}
 		}
 
 		return numbers;
@@ -290,15 +295,9 @@ public class NumberReader {
 		adjustValues();
 
 		Numbers n = new Numbers();
-		n.setRnodNumber(nodeCounter);
-
-		n.setLeftNumberStyle(leftStyle);
-		n.setLeftStart(leftStart);
-		n.setLeftEnd(leftEnd);
-
-		n.setRightNumberStyle(rightStyle);
-		n.setRightStart(rightStart);
-		n.setRightEnd(rightEnd);
+		n.setIndex(nodeCounter);
+		n.setNumbers(Numbers.LEFT, leftStyle, leftStart, leftEnd);
+		n.setNumbers(Numbers.RIGHT, rightStyle, rightStart, rightEnd);
 
 		numbers.add(n);
 		nodeCounter++;
@@ -331,26 +330,11 @@ public class NumberReader {
 		adjustValues();
 
 		Numbers n = new Numbers();
-		if (leftStyle == NONE) {
-			n.setRnodNumber(nodeCounter);
-			n.setRightNumberStyle(rightStyle);
-			n.setRightStart(rightStart);
-			n.setRightEnd(rightEnd);
-
-			n.setLeftNumberStyle(NONE);
-			n.setLeftStart(-1);
-			n.setLeftEnd(-1);
-		}
-		else {
-			n.setRnodNumber(nodeCounter);
-			n.setLeftNumberStyle(leftStyle);
-			n.setLeftStart(leftStart);
-			n.setLeftEnd(leftEnd);
-
-			n.setRightNumberStyle(NONE);
-			n.setRightStart(-1);
-			n.setRightEnd(-1);
-		}
+		n.setIndex(nodeCounter);
+		if (leftStyle == NONE) 
+			n.setNumbers(Numbers.RIGHT, rightStyle, rightStart, rightEnd);
+		else 
+			n.setNumbers(Numbers.LEFT, leftStyle, leftStart, leftEnd);
 		numbers.add(n);
 		nodeCounter++;
 	}
diff --git a/test/main/NumberRangeTest.java b/test/main/NumberRangeTest.java
index 819458e..a991c13 100644
--- a/test/main/NumberRangeTest.java
+++ b/test/main/NumberRangeTest.java
@@ -72,7 +72,7 @@ public class NumberRangeTest {
 		List<Numbers> numbers = new ArrayList<Numbers>();
 		for (String s : strings) {
 			Numbers n = new Numbers(s);
-			n.setRnodNumber(n.getNodeNumber());
+			n.setIndex(n.getNodeNumber());
 			numbers.add(n);
 		}
 
@@ -84,13 +84,12 @@ public class NumberRangeTest {
 		byte[] bytes = new byte[bitWriter.getLength()];
 		System.arraycopy(bitWriter.getBytes(), 0, bytes, 0, bytes.length);
 		NumberReader nr = new NumberReader(new BitReader(bytes));
-
-		nr.setNumberOfNodes(numbers.size());
+		nr.setNumberOfNodes(numbers.get(numbers.size()-1).getIndex() + 1);
 		List<Numbers> list = nr.readNumbers(np.getSwapped());
 
 		// Have to fix up the node numbers
 		for (Numbers n : list) {
-			n.setNodeNumber(n.getRnodNumber());
+			n.setNodeNumber(n.getIndex());
 		}
 
 		// Test that they are the same.
diff --git a/test/uk/me/parabola/imgfmt/app/net/NumberPreparerTest.java b/test/uk/me/parabola/imgfmt/app/net/NumberPreparerTest.java
index f373652..d4433ca 100644
--- a/test/uk/me/parabola/imgfmt/app/net/NumberPreparerTest.java
+++ b/test/uk/me/parabola/imgfmt/app/net/NumberPreparerTest.java
@@ -265,10 +265,10 @@ public class NumberPreparerTest {
 
 		BitReader br = new BitReader(bytes);
 		NumberReader nr = new NumberReader(br);
-		nr.setNumberOfNodes(numbers.size());
+		nr.setNumberOfNodes(numbers.get(numbers.size()-1).getIndex() + 1);
 		List<Numbers> list = nr.readNumbers(swapped);
 		for (Numbers n : list)
-			n.setNodeNumber(n.getRnodNumber());
+			n.setNodeNumber(n.getIndex());
 
 		return list;
 	}
@@ -277,7 +277,7 @@ public class NumberPreparerTest {
 		List<Numbers> numbers = new ArrayList<Numbers>();
 		for (String s : specs) {
 			Numbers n = new Numbers(s);
-			n.setRnodNumber(n.getNodeNumber());
+			n.setIndex(n.getNodeNumber());
 			numbers.add(n);
 		}
 		return numbers;
diff --git a/test/uk/me/parabola/imgfmt/app/net/NumbersTest.java b/test/uk/me/parabola/imgfmt/app/net/NumbersTest.java
new file mode 100644
index 0000000..c7b500c
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/net/NumbersTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+package uk.me.parabola.imgfmt.app.net;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests  to verify that the plausibility checks are working
+ */
+public class NumbersTest {
+
+	@Test
+	public void testOKOddEvenOverlap() {
+		String spec = "0,O,1,7,E,2,12";
+		Numbers numbers = new Numbers(spec);
+		assertTrue(numbers.isPlausible());
+	}
+	@Test
+	public void testBothBothNoOverlap() {
+		String spec = "0,B,1,7,B,8,15";
+		Numbers numbers = new Numbers(spec);
+		assertTrue(numbers.isPlausible());
+	}
+	@Test
+	public void testBothEvenNoOverlap() {
+		String spec = "0,B,1,7,E,8,16";
+		Numbers numbers = new Numbers(spec);
+		assertTrue(numbers.isPlausible());
+	}
+	@Test
+	public void testBothEvenNoOverlapNotEven() {
+		String spec = "0,B,1,7,E,8,15";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testOverlapAtStartEnd() {
+		String spec = "0,B,1,7,B,7,16";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testBothEvenOverlap() {
+		String spec = "0,B,1,7,E,6,16";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testRangeLargeNumbersOK() {
+		String spec = "0,B,10012,10024,N,0,0";
+		Numbers numbers = new Numbers(spec);
+		assertTrue(numbers.isPlausible());
+	}
+	@Test
+	public void testRangeLargeNumbersNotOK() {
+		String spec = "0,B,10012,1000240,N,0,0";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testNotOK1() {
+		String spec = "0,B,10,23,O,15,15";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testNotOK2() {
+		String spec = "0,O,15,15,B,10,23";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testSingleNumBothSides() {
+		String spec = "0,O,15,15,O,15,15";
+		Numbers numbers = new Numbers(spec);
+		assertTrue(numbers.isPlausible());
+	}
+	@Test
+	public void testSingleNumOneSideEqualStartOrEndOtherSide1() {
+		String spec = "0,O,13,15,O,15,15";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testSingleNumOneSideEqualStartOrEndOtherSide2() {
+		String spec = "0,O,15,15,O,13,15";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testSingleNumOneSideEqualStartOrEndOtherSide3() {
+		String spec = "0,O,15,13,O,15,15";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testSingleNumOneSideEqualStartOrEndOtherSide4() {
+		String spec = "0,O,15,15,O,15,13";
+		Numbers numbers = new Numbers(spec);
+		assertFalse(numbers.isPlausible());
+	}
+	@Test
+	public void testSingleDifferentNumEachSide() {
+		String spec = "0,O,15,15,O,13,13";
+		Numbers numbers = new Numbers(spec);
+		assertTrue(numbers.isPlausible());
+	}
+	
+	@Test
+	public void testCountMatchesValid() {
+		String spec = "0,O,1,7,E,2,12";
+		Numbers numbers = new Numbers(spec);
+		assertEquals(1,numbers.countMatches(1));
+		assertEquals(0,numbers.countMatches(13));
+	}
+	@Test
+	public void testCountMatchesGap() {
+		String spec = "0,B,1,7,B,9,12";
+		Numbers numbers = new Numbers(spec);
+		assertEquals(1,numbers.countMatches(1));
+		assertEquals(1,numbers.countMatches(7));
+		assertEquals(0,numbers.countMatches(8));
+		assertEquals(0,numbers.countMatches(13));
+	}
+	
+}
diff --git a/test/uk/me/parabola/mkgmap/general/MapPointKdTreeTest.java b/test/uk/me/parabola/util/KdTreeTest.java
similarity index 86%
rename from test/uk/me/parabola/mkgmap/general/MapPointKdTreeTest.java
rename to test/uk/me/parabola/util/KdTreeTest.java
index 15bd6b7..646ea2b 100644
--- a/test/uk/me/parabola/mkgmap/general/MapPointKdTreeTest.java
+++ b/test/uk/me/parabola/util/KdTreeTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012.
+ * Copyright (C) 2014 Gerd Petermann
  *
  * 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
@@ -10,19 +10,20 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  */
-
-package uk.me.parabola.mkgmap.general;
+package uk.me.parabola.util;
 
 import static org.junit.Assert.assertFalse;
 import org.junit.Test;
 
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.general.MapPoint;
+import uk.me.parabola.util.KdTree;
 
-public class MapPointKdTreeTest {
+public class KdTreeTest {
 
 	@Test
 	public void TestFindNextPoint(){
-        MapPointKdTree t = new MapPointKdTree( );
+        KdTree<MapPoint> t = new KdTree<>( );
         
         int [][]test = {{70,20}, {50,40}, {90,60}, {20,30}, {40,70}, {80,10}, {-10,20}, {-30,-40} }  ;
         Coord []testCoords = new Coord[test.length]; 
@@ -48,7 +49,7 @@ public class MapPointKdTreeTest {
         			}
         		}
         		toFind.setLocation(co);
-        		MapPoint next = t.findNextPoint(toFind);
+        		MapPoint next = (MapPoint) t.findNextPoint(toFind);
     			double dist =  next.getLocation().distanceInDegreesSquared(co);
     			double delta = Math.abs(dist - minDist); 
     			// if this test fails because 

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