use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class ExtNumbers method splitInterval.
/**
* 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);
log.debug("possible solution: split segment with length", formatLen(segmentLength), "near", formatLen(wantedFraction * segmentLength));
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));
double distToLine = toAdd.getDisplayedCoord().distToLineSegment(c1.getDisplayedCoord(), c2.getDisplayedCoord());
log.info("adding number node at", toAdd.toDegreeString(), "to split, dist to line is", formatLen(distToLine));
}
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);
}
// the other side
recalcHousePositions(getHouses(!left));
}
}
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;
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class ExtNumbers method tryAddNumberNode.
/**
* 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");
// can't add a node
return this;
}
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;
// dist to first
double len1 = segmentLength * minFraction0To1;
double len2 = segmentLength * maxFraction0To1;
double len3 = (1 - maxFraction0To1) * segmentLength;
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;
List<Double> wantedFractions = new ArrayList<>();
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 = midFraction * 2 - (midFraction > 0.5 ? 1 : 0);
allowSplitBetween = false;
} else {
if (len1 > MAX_LOCATE_ERROR / 2) {
// create empty segment at start
wantedFractions.add(minFraction0To1 * 0.999);
forceEmpty = true;
}
if (len3 > MAX_LOCATE_ERROR / 2) {
// create empty segment at end
if (!wantedFractions.isEmpty() && len3 > len1)
wantedFractions.add(0, maxFraction0To1 * 1.001);
else
wantedFractions.add(maxFraction0To1 * 1.001);
forceEmpty = true;
}
}
}
if (wantedFractions.isEmpty())
wantedFractions.add(wantedFraction);
double usedFraction = 0;
double bestDist = Double.MAX_VALUE;
for (int w = 0; w < wantedFractions.size(); w++) {
wantedFraction = wantedFractions.get(w);
double partLen = wantedFraction * segmentLength;
double shorterLen = Math.min(partLen, segmentLength - partLen);
if (shorterLen < 10) {
if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
return dupNode(midFraction, SR_FIX_ERROR);
double splitFrac = len1 < len3 ? minFraction0To1 : maxFraction0To1;
return dupNode(splitFrac, SR_OPT_LEN);
}
double expectedError = c1.getDisplayedCoord().distance(new Coord(c1.getLatitude() + 1, c1.getLongitude()));
double maxDistBefore = expectedError;
double maxDistAfter = expectedError;
if (wantedFraction < minFraction0To1) {
maxDistAfter = 0;
}
if (wantedFraction > maxFraction0To1) {
maxDistBefore = 0;
}
for (; ; ) {
Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
Map<Double, List<Coord>> candidates = rasterLineNearPoint2(c1, c2, wanted, maxDistBefore, maxDistAfter);
boolean foundGood = false;
for (Entry<Double, List<Coord>> entry : candidates.entrySet()) {
if (foundGood)
break;
bestDist = entry.getKey();
for (Coord candidate : entry.getValue()) {
toAdd = candidate;
usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
if (usedFraction <= 0 || usedFraction >= 1)
toAdd = null;
else if (usedFraction > minFraction0To1 && wantedFraction < minFraction0To1 || usedFraction < maxFraction0To1 && wantedFraction > maxFraction0To1) {
toAdd = null;
} else if (allowSplitBetween == false && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1) {
toAdd = null;
} else {
if (bestDist > 0.2) {
double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
if (Math.abs(angle) > 3) {
toAdd = null;
continue;
}
}
foundGood = true;
break;
}
}
}
if (foundGood) {
break;
}
toAdd = null;
boolean tryAgain = false;
if (maxDistBefore > 0 && maxDistBefore < segmentLength * wantedFraction) {
maxDistBefore *= 2;
tryAgain = true;
}
if (maxDistAfter > 0 && maxDistAfter < segmentLength * (1 - wantedFraction)) {
maxDistAfter *= 2;
tryAgain = true;
}
if (!tryAgain)
break;
}
if (toAdd != null)
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 = midFraction;
if (splitFrac <= 0.5) {
if (splitFrac * segmentLength > MAX_LOCATE_ERROR)
splitFrac = Math.max(minFraction0To1, MAX_LOCATE_ERROR / segmentLength);
} else {
if ((1 - splitFrac) * segmentLength > MAX_LOCATE_ERROR)
splitFrac = Math.min(maxFraction0To1, (segmentLength - MAX_LOCATE_ERROR) / segmentLength);
}
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;
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class HousenumberGenerator method identifyServiceRoads.
/**
* 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;
if (road.getStreet() == 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);
}
}
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;
// don't process again
unnamedRoads.set(j, null);
break;
}
String possibleName = roadNamesByNodeIds.get(co.getId());
if (possibleName == null)
continue;
if (name == null)
name = possibleName;
else if (name.equals(possibleName) == false) {
name = null;
// don't process again
unnamedRoads.set(j, null);
break;
}
}
if (name != null) {
named++;
road.setStreet(name);
namedRoads.add(road);
// don't process again
unnamedRoads.set(j, null);
}
}
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);
}
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;
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class HousenumberGenerator method findClosestRoadSegment.
/**
* 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());
}
}
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class HousenumberGroup method housesFormAGroup.
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;
}
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;
// road are on a straight line
if (ratio2 > 0.9)
return true;
if (ratio2 < 0.666)
return false;
return true;
}
Aggregations