/*
* 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.CityInfo;
import uk.me.parabola.mkgmap.general.MapRoad;
import uk.me.parabola.mkgmap.general.ZipCodeInfo;
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 TKM_STREET = TagDict.
getInstance().
xlate("mkgmap:street");
private static final short TKM_HOUSENUMBER = TagDict.
getInstance().
xlate("mkgmap:housenumber");
private static final short TK_ADDR_INTERPOLATION = TagDict.
getInstance().
xlate("addr:interpolation");
public HousenumberIvl
(String streetName, Way interpolationWay,
Node n1,
Node n2
) {
this.
streetName = streetName
;
this.
interpolationWay = interpolationWay
;
this.
n1 = n1
;
this.
n2 = n2
;
}
public void setPoints
(List<Coord
> points
) {
this.
points =
new ArrayList<>(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 calcSteps
() {
if (start
< end
){
steps =
(end - start
) / step -
1;
} else {
steps =
(start - end
) / step -
1;
}
}
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()) && knownHouses
[i
].
hasAlternativeRoad()) {
HousenumberMatch testx =
new HousenumberMatch
(knownHouses
[i
]);
MapRoad r = knownHouses
[i
].
getAlternativeRoads().
remove(0);
if (streetName.
equals(r.
getStreet())){
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()))
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())
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()) ||
!streetName.
equals(knownHouses
[1].
getRoad().
getStreet())) {
log.
warn("cannot find reasonable road for both nodes",streetName,
this);
return false;
}
hasMultipleRoads =
true;
return true;
}
for (int i =
0; i
< 2; i++
) {
if (test
[i
].
getSegmentFrac() < 0 || test
[i
].
getSegmentFrac() > 1) {
// might still be one road that bends but that will not cause trouble
hasMultipleRoads =
true;
return true;
}
}
// we found a single road instance that should be used for all interpolated numbers
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
[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.
toBrowseURL(),
"from road",dest.
getRoad(),
"to road",source.
getRoad());
else
log.
info("moving",streetName,dest.
getSign(),dest.
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;
CityInfo ci = knownHouses
[0].
getCityInfo();
ZipCodeInfo zip = knownHouses
[0].
getZipCode();
if (ci
!=
null && !ci.
equals(knownHouses
[1].
getCityInfo()))
log.
warn("addr:interpolation way connects houses in different cities",streetName,
this,
"using city",ci,
"for all interpolated adresses");
if (zip
!=
null && !zip.
equals(knownHouses
[1].
getZipCode()))
log.
warn("addr:interpolation way connects houses with differnt zip codes",streetName,
this,
"using zip code",zip,
"for all interpolated adresses");
for (Coord co : interpolatedPoints
) {
hn += usedStep
;
Node generated =
new Node(interpolationWay.
getId(), co
);
generated.
markAsGeneratedFrom(interpolationWay
);
generated.
addTag(TKM_STREET, streetName
);
String number =
String.
valueOf(hn
);
generated.
addTag(TKM_HOUSENUMBER, number
);
// TODO: maybe add check that city info and zip code of both houses is equal ?
// what if not ?
HousenumberElem houseElem =
new HousenumberElem
(generated, ci
);
houseElem.
setHousenumber(hn
);
houseElem.
setZipCode(zip
);
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
){
log.
warn("interpolated house is not close to expected road",
this,house
);
distanceWarningIssued =
true;
}
continue;
}
house.
calcRoadSide();
}
house.
setInterpolated(true);
houses.
add(house
);
}
if (log.
isDebugEnabled()){
String addrInterpolationMethod = interpolationWay.
getTag(TK_ADDR_INTERPOLATION
);
if (!hasMultipleRoads
)
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
){
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 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
) || he.
getElement().
getId() != id
)
return false;
knownHouses
[i
] =
(HousenumberMatch
) he
;
knownHouses
[i
].
incIntervalInfoRefs();
}
return true;
}
public HousenumberMatch
[] getHouseNodes
(){
return knownHouses
;
}
/**
* Check if an address node can be used to calculate new intervals
* @param houseToAdd a single address from OSM data
* @return null in case of error or an array with two new {@link HousenumberIvl} instances
*/
public HousenumberIvl
[] trySplitAt
(HousenumberMatch houseToAdd
) {
if (houseToAdd.
isInterpolated())
return null;
if (houseToAdd.
getRoad() != knownHouses
[0].
getRoad() && houseToAdd.
getRoad() != knownHouses
[1].
getRoad())
return null;
HousenumberMatch s = knownHouses
[0];
HousenumberMatch e = knownHouses
[1];
if (s.
getSegment() > e.
getSegment() || s.
getSegment() == e.
getSegment() && s.
getSegmentFrac() > e.
getSegmentFrac()){
s = knownHouses
[1];
e = knownHouses
[0];
}
if (houseToAdd.
getSegment() < s.
getSegment() || houseToAdd.
getSegment() > e.
getSegment())
return null;
if (houseToAdd.
getSegment() == s.
getSegment() && houseToAdd.
getSegmentFrac() < s.
getSegmentFrac())
return null;
if (houseToAdd.
getSegment() == e.
getSegment() && houseToAdd.
getSegmentFrac() > e.
getSegmentFrac())
return null;
for (int i =
0; i+
1 < points.
size(); i++
){
Coord c1 = points.
get(i
);
Coord c2 = points.
get(i +
1);
double frac = HousenumberGenerator.
getFrac(c1, c2, houseToAdd.
getLocation());
if (frac
< 0 || frac
> 1)
continue;
HousenumberIvl
[] ivls =
new HousenumberIvl
[2];
HousenumberMatch hnm =
null;
if (houseToAdd.
element instanceof Node){
hnm = houseToAdd
;
}
else {
// create a Node instance
Node toAdd =
new Node(houseToAdd.
getElement().
getId(), houseToAdd.
getLocation());
toAdd.
markAsGeneratedFrom(houseToAdd.
getElement());
toAdd.
copyTags(houseToAdd.
element);
HousenumberElem hnElem =
new HousenumberElem
(toAdd, houseToAdd.
getCityInfo());
hnm =
new HousenumberMatch
(hnElem
);
hnm.
setZipCode(houseToAdd.
getZipCode());
HousenumberGenerator.
findClosestRoadSegment(hnm, houseToAdd.
getRoad(), houseToAdd.
getSegment(), houseToAdd.
getSegment());
}
List<Coord
> points1 =
new ArrayList<>();
List<Coord
> points2 =
new ArrayList<>();
points1.
addAll(points.
subList(0, i+
1));
points1.
add(houseToAdd.
getLocation());
points2.
add(houseToAdd.
getLocation());
points2.
addAll(points.
subList(i+
1, points.
size()));
ivls
[0] =
new HousenumberIvl
(streetName, interpolationWay, n1,
(Node)hnm.
element);
ivls
[0].
setStart(knownHouses
[0].
getHousenumber());
ivls
[0].
setEnd(houseToAdd.
getHousenumber());
ivls
[0].
setStep(step
);
ivls
[0].
calcSteps();
ivls
[0].
setPoints(points1
);
ivls
[0].
addHousenumberMatch(knownHouses
[0]);
ivls
[0].
addHousenumberMatch(hnm
);
ivls
[1] =
new HousenumberIvl
(streetName, interpolationWay,
(Node) hnm.
element, n2
);
ivls
[1].
setStart(houseToAdd.
getHousenumber());
ivls
[1].
setEnd(knownHouses
[1].
getHousenumber());
ivls
[1].
setStep(step
);
ivls
[1].
calcSteps();
ivls
[1].
setPoints(points2
);
ivls
[1].
addHousenumberMatch(knownHouses
[1]);
ivls
[1].
addHousenumberMatch(hnm
);
return ivls
;
}
return null;
}
}