use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class StyledConverter method setHighwayCounts.
/**
* Increment the highway counter for each coord of each road.
* As a result, all road junctions have a count > 1.
*/
private void setHighwayCounts() {
log.info("Maintaining highway counters");
long lastId = 0;
List<Way> dupIdHighways = new ArrayList<>();
for (ConvertedWay cw : roads) {
if (!cw.isValid())
continue;
Way way = cw.getWay();
if (way.getId() == lastId) {
log.debug("Road with identical id:", way.getId());
dupIdHighways.add(way);
continue;
}
lastId = way.getId();
List<Coord> points = way.getPoints();
for (Coord p : points) {
p.incHighwayCount();
}
}
for (Way way : dupIdHighways) {
List<Coord> points = way.getPoints();
// increase the highway counter of the first and last point
points.get(0).incHighwayCount();
points.get(points.size() - 1).incHighwayCount();
// for all other points increase the counter only if other roads are connected
for (int i = 1; i < points.size() - 1; i++) {
Coord p = points.get(i);
if (p.getHighwayCount() > 1) {
// this is a crossroads - mark that the duplicated way is also part of it
p.incHighwayCount();
}
}
}
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class StyledConverter method checkRoundabout.
/**
* Check if roundabout has correct direction. Set driveOnRight or
* driveOnLeft is not yet set.
*/
private void checkRoundabout(ConvertedWay cw) {
if (cw.isRoundabout() == false)
return;
Way way = cw.getWay();
List<Coord> points = way.getPoints();
// check", check its direction
if (checkRoundabouts && points.size() > 2 && !way.tagIsLikeYes("mkgmap:no-dir-check") && !way.tagIsLikeNo("mkgmap:dir-check")) {
Coord centre = way.getCofG();
int dir = 0;
// check every third segment
for (int i = 0; (i + 1) < points.size(); i += 3) {
Coord pi = points.get(i);
Coord pi1 = points.get(i + 1);
// don't check segments that are very short
if (pi.distance(centre) > 2.5 && pi.distance(pi1) > 2.5) {
// determine bearing from segment that starts with
// point i to centre of roundabout
double a = pi.bearingTo(pi1);
double b = pi.bearingTo(centre) - a;
while (b > 180) b -= 360;
while (b < -180) b += 360;
// degrees consider it trustworthy
if (b >= 15 && b < 165)
++dir;
else if (b <= -15 && b > -165)
--dir;
}
}
if (dir == 0)
log.info("Roundabout segment " + way.getId() + " direction unknown (see " + points.get(0).toOSMURL() + ")");
else {
boolean clockwise = dir > 0;
if (points.get(0) == points.get(points.size() - 1)) {
// roundabout is a loop
if (driveOnLeft == true && !clockwise || driveOnLeft == false && clockwise) {
log.warn("Roundabout " + way.getId() + " direction is wrong - reversing it (see " + centre.toOSMURL() + ")");
way.reverse();
}
} else if (driveOnLeft == true && !clockwise || driveOnLeft == false && clockwise) {
// roundabout is a line
log.warn("Roundabout segment " + way.getId() + " direction looks wrong (see " + points.get(0).toOSMURL() + ")");
}
}
}
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class StyledConverter method addRoadWithoutLoops.
private void addRoadWithoutLoops(ConvertedWay cw) {
Way way = cw.getWay();
List<Integer> nodeIndices = new ArrayList<>();
List<Coord> points = way.getPoints();
if (points.size() < 2) {
log.warn("road has < 2 points", way.getId(), "(discarding)");
return;
}
Way trailingWay = null;
String debugWayName = way.getDebugName();
// collect the Way's nodes and also split the way if any
// inter-node arc length becomes excessive
double arcLength = 0;
// detect if it would be split by the LineSizeSplitterFilter
class WayBBox {
int minLat = Integer.MAX_VALUE;
int maxLat = Integer.MIN_VALUE;
int minLon = Integer.MAX_VALUE;
int maxLon = Integer.MIN_VALUE;
void addPoint(Coord co) {
int lat = co.getLatitude();
if (lat < minLat)
minLat = lat;
if (lat > maxLat)
maxLat = lat;
int lon = co.getLongitude();
if (lon < minLon)
minLon = lon;
if (lon > maxLon)
maxLon = lon;
}
boolean tooBig() {
return LineSizeSplitterFilter.testDims(maxLat - minLat, maxLon - minLon) >= 1.0;
}
}
WayBBox wayBBox = new WayBBox();
for (int i = 0; i < points.size(); ++i) {
Coord p = points.get(i);
wayBBox.addPoint(p);
// the arc length between nodes
if ((i + 1) < points.size()) {
Coord nextP = points.get(i + 1);
double d = p.distance(nextP);
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);
// points so the loop will now terminate
if (log.isInfoEnabled())
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);
// points so the loop will now terminate
if (log.isInfoEnabled())
log.info("Splitting way", debugWayName, "at", points.get(i).toOSMURL(), "to limit the size of its bounding box");
} else {
if (p.getHighwayCount() > 1) {
// point is a node so zero arc length
arcLength = 0;
}
arcLength += d;
}
}
if (p.getHighwayCount() > 1) {
// this point is a node connecting highways
CoordNode coordNode = nodeIdMap.get(p);
if (coordNode == null) {
// assign a node id
coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary());
nodeIdMap.put(p, coordNode);
}
if (p instanceof CoordPOI) {
// check if this poi should be converted to a route restriction
CoordPOI cp = (CoordPOI) p;
if (cp.getConvertToViaInRouteRestriction()) {
String wayPOI = way.getTag(WAY_POI_NODE_IDS);
if (wayPOI != null && wayPOI.contains("[" + cp.getNode().getId() + "]")) {
byte nodeAccess = AccessTagsAndBits.evalAccessTags(cp.getNode());
if (nodeAccess != cw.getAccess()) {
List<Way> wayList = poiRestrictions.get(cp.getNode());
if (wayList == null) {
wayList = new ArrayList<>();
poiRestrictions.put(cp.getNode(), wayList);
}
wayList.add(way);
}
}
}
}
// add this index to node Indexes (should not already be there)
assert !nodeIndices.contains(i) : debugWayName + " has multiple nodes for point " + i + " new node is " + p.toOSMURL();
nodeIndices.add(i);
if ((i + 1) < points.size() && nodeIndices.size() == MAX_NODES_IN_WAY) {
// limit
assert trailingWay == null : "trailingWay not null #7";
trailingWay = splitWayAt(way, i);
// points so the loop will now terminate
if (log.isInfoEnabled())
log.info("Splitting way", debugWayName, "at", points.get(i).toOSMURL(), "as it has at least", MAX_NODES_IN_WAY, "nodes");
}
}
}
MapLine line = new MapLine();
elementSetup(line, cw.getGType(), way);
line.setPoints(points);
MapRoad road = new MapRoad(nextRoadId++, way.getId(), line);
if (routable == false)
road.skipAddToNOD(true);
boolean doFlareCheck = true;
if (cw.isRoundabout()) {
road.setRoundabout(true);
doFlareCheck = false;
}
if (way.tagIsLikeYes("mkgmap:synthesised")) {
road.setSynthesised(true);
doFlareCheck = false;
}
if (way.tagIsLikeNo("mkgmap:flare-check")) {
doFlareCheck = false;
} else if (way.tagIsLikeYes("mkgmap:flare-check")) {
doFlareCheck = true;
}
road.doFlareCheck(doFlareCheck);
// set road parameters
// copy road class and road speed
road.setRoadClass(cw.getRoadClass());
road.setSpeed(cw.getRoadSpeed());
if (cw.isOneway()) {
road.setDirection(true);
road.setOneway();
}
road.setAccess(cw.getAccess());
// does the road have a carpool lane?
if (cw.isCarpool())
road.setCarpoolLane();
if (cw.isThroughroute() == false)
road.setNoThroughRouting();
if (cw.isToll())
road.setToll();
// by default, ways are paved
if (cw.isUnpaved())
road.paved(false);
// by default, way's are not ferry routes
if (cw.isFerry())
road.ferry(true);
int numNodes = nodeIndices.size();
if (way.isViaWay() && numNodes > 2) {
List<RestrictionRelation> rrList = wayRelMap.get(way.getId());
for (RestrictionRelation rr : rrList) {
rr.updateViaWay(way, nodeIndices);
}
}
if (numNodes > 0) {
// replace Coords that are nodes with CoordNodes
for (int i = 0; i < numNodes; ++i) {
int n = nodeIndices.get(i);
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";
boolean boundary = coord.getOnBoundary();
if (boundary && log.isInfoEnabled()) {
log.info("Way", debugWayName + "'s point #" + n, "at", coord.toOSMURL(), "is a boundary node");
}
points.set(n, thisCoordNode);
}
}
if (roadLog.isInfoEnabled()) {
// shift the bits so that they have the correct position
int cmpAccess = (road.getRoadDef().getTabAAccess() & 0xff) + ((road.getRoadDef().getTabAAccess() & 0xc000) >> 6);
if (road.isDirection()) {
cmpAccess |= 1 << 10;
}
String access = String.format("%11s", Integer.toBinaryString(cmpAccess)).replace(' ', '0');
roadLog.info(String.format("%19d 0x%-2x %11s %6d %6d %6d %s", way.getId(), road.getType(), access, road.getRoadDef().getRoadClass(), road.getRoadDef().getRoadSpeed(), road.getPoints().size(), Arrays.toString(road.getLabels())));
}
// add the road to the housenumber generator
// it will add the road later on to the lineAdder
housenumberGenerator.addRoad(way, road);
if (trailingWay != null)
addRoadWithoutLoops(new ConvertedWay(cw, trailingWay));
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class WrongAngleFixer method removeWrongAngles.
/**
* Find wrong angles caused by rounding to map units. Try to fix them by
* moving, removing or merging points.
* @param roads list with routable ways or null, if lines should be optimized
* @param lines list with non-routable ways
* @param modifiedRoads map of modified routable ways (modified by this routine)
* @param deletedRoads set of ids of deleted routable ways (modified by this routine)
* @param restrictions Map with restriction relations. The restriction relations may be modified by this routine
*/
private void removeWrongAngles(List<ConvertedWay> roads, List<ConvertedWay> lines, HashMap<Long, ConvertedWay> modifiedRoads, HashSet<Long> deletedRoads, List<RestrictionRelation> restrictions) {
// replacements maps those nodes that have been replaced to
// the node that replaces them
Map<Coord, Coord> replacements = new IdentityHashMap<>();
final HashSet<Coord> changedPlaces = new HashSet<>();
int numNodesMerged = 0;
HashSet<Way> waysWithBearingErrors = new HashSet<>();
HashSet<Long> waysThatMapToOnePoint = new HashSet<>();
Way lastWay = null;
List<ConvertedWay> convertedWays = (roads != null) ? roads : lines;
boolean anotherPassRequired = true;
for (pass = 1; pass < 20; pass++) {
if (!anotherPassRequired && !extraPass)
break;
anotherPassRequired = false;
log.info("Removing wrong angles - PASS", pass);
writeOSM(((mode == MODE_LINES) ? "lines" : "roads") + "_pass_" + pass, convertedWays);
// Step 1: detect points which are parts of line segments with wrong bearings
lastWay = null;
for (ConvertedWay cw : convertedWays) {
if (!cw.isValid() || cw.isOverlay())
continue;
Way way = cw.getWay();
if (way.equals(lastWay))
continue;
if (pass != 1 && waysWithBearingErrors.contains(way) == false)
continue;
lastWay = way;
List<Coord> points = way.getPoints();
// scan through the way's points looking for line segments with big
// bearing errors
Coord prev = null;
if (points.get(0) == points.get(points.size() - 1) && points.size() >= 2)
prev = points.get(points.size() - 2);
boolean hasNonEqualPoints = false;
for (int i = 0; i < points.size(); ++i) {
Coord p = points.get(i);
if (pass == 1)
p.setRemove(false);
p = getReplacement(p, way, replacements);
if (i == 0 || i == points.size() - 1) {
p.setEndOfWay(true);
}
if (prev != null) {
if (pass == 1 && p.equals(prev) == false)
hasNonEqualPoints = true;
double err = calcBearingError(p, prev);
if (err >= MAX_BEARING_ERROR) {
// bearing error is big
p.setPartOfBadAngle(true);
prev.setPartOfBadAngle(true);
}
}
prev = p;
}
if (pass == 1 && hasNonEqualPoints == false) {
waysThatMapToOnePoint.add(way.getId());
log.info("all points of way", way.toBrowseURL(), "are rounded to equal map units");
}
}
// Step 2: collect the line segments that are connected to critical points
IdentityHashMap<Coord, CenterOfAngle> centerMap = new IdentityHashMap<>();
// needed for ordered processing
List<CenterOfAngle> centers = new ArrayList<>();
Map<Coord, Set<Way>> overlaps = new HashMap<>();
lastWay = null;
for (ConvertedWay cw : convertedWays) {
if (!cw.isValid() || cw.isOverlay())
continue;
Way way = cw.getWay();
if (way.equals(lastWay))
continue;
if (pass != 1 && waysWithBearingErrors.contains(way) == false)
continue;
lastWay = way;
boolean wayHasSpecialPoints = false;
List<Coord> points = way.getPoints();
// scan through the way's points looking for line segments with big
// bearing errors
Coord prev = null;
if (points.get(0) == points.get(points.size() - 1) && points.size() >= 2)
prev = points.get(points.size() - 2);
for (int i = 0; i < points.size(); ++i) {
Coord p = points.get(i);
if (prev != null) {
if (p == prev) {
points.remove(i);
--i;
if (mode == MODE_ROADS)
modifiedRoads.put(way.getId(), cw);
continue;
}
if (p.isPartOfBadAngle() || prev.isPartOfBadAngle()) {
wayHasSpecialPoints = true;
// save both points with their neighbour
Coord p1 = prev;
Coord p2 = p;
CenterOfAngle coa1 = getOrCreateCenter(p, way, centerMap, centers, overlaps);
CenterOfAngle coa2 = getOrCreateCenter(prev, way, centerMap, centers, overlaps);
coa1.addNeighbour(coa2);
coa2.addNeighbour(coa1);
if (points.size() == 2) {
// way has only two points, don't merge them
coa1.addBadMergeCandidate(coa2);
}
if (mode == MODE_ROADS) {
if (p1.getHighwayCount() >= 2 && p2.getHighwayCount() >= 2) {
if (cw.isRoundabout()) {
// avoid to merge exits of roundabouts
coa1.addBadMergeCandidate(coa2);
}
}
}
}
}
prev = p;
}
if (pass == 1 && wayHasSpecialPoints)
waysWithBearingErrors.add(way);
}
markOverlaps(overlaps, centers);
overlaps.clear();
// Step 3: Update list of ways with bearing errors or points next to them
lastWay = null;
for (ConvertedWay cw : convertedWays) {
if (!cw.isValid() || cw.isOverlay())
continue;
Way way = cw.getWay();
if (way.equals(lastWay))
continue;
lastWay = way;
if (waysWithBearingErrors.contains(way))
continue;
List<Coord> points = way.getPoints();
// bearing errors
for (Coord p : points) {
if (p.getHighwayCount() < 2)
continue;
if (centerMap.containsKey(p)) {
waysWithBearingErrors.add(way);
break;
}
}
}
log.info("pass " + pass + ": analysing " + centers.size() + " points with bearing problems.");
// Return to GC
centerMap = null;
// Step 4: try to correct the errors
List<CenterOfAngle> checkAgainList = null;
boolean tryMerge = false;
while (true) {
checkAgainList = new ArrayList<>();
for (CenterOfAngle coa : centers) {
// reset flag for next pass
coa.center.setPartOfBadAngle(false);
if (coa.getCurrentLocation(replacements) == null)
// removed center
continue;
if (coa.isOK(replacements) == false) {
boolean changed = coa.tryChange(replacements, tryMerge);
if (changed) {
if (DEBUG_PATH != null)
changedPlaces.add(coa.center);
continue;
}
checkAgainList.add(coa);
}
}
if (tryMerge)
// leave when 2nd pass finished
break;
tryMerge = true;
centers = checkAgainList;
}
// Step 5: apply the calculated corrections to the ways
lastWay = null;
boolean lastWayModified = false;
ConvertedWay lastConvertedWay = null;
for (ConvertedWay cw : convertedWays) {
if (!cw.isValid() || cw.isOverlay())
continue;
Way way = cw.getWay();
if (waysWithBearingErrors.contains(way) == false)
continue;
List<Coord> points = way.getPoints();
if (way.equals(lastWay)) {
if (lastWayModified) {
points.clear();
points.addAll(lastWay.getPoints());
if (cw.isReversed() != lastConvertedWay.isReversed())
Collections.reverse(points);
}
continue;
}
lastWay = way;
lastConvertedWay = cw;
lastWayModified = false;
// loop backwards because we may delete points
for (int i = points.size() - 1; i >= 0; i--) {
Coord p = points.get(i);
if (p.isToRemove()) {
points.remove(i);
anotherPassRequired = true;
lastWayModified = true;
if (i > 0 && i < points.size()) {
// special case: handle micro loop
if (points.get(i - 1) == points.get(i))
points.remove(i);
}
continue;
}
// check if this point is to be replaced because
// it was previously moved
Coord replacement = getReplacement(p, way, replacements);
if (p == replacement)
continue;
if (p.isViaNodeOfRestriction()) {
// make sure that we find the restriction with the new coord instance
replacement.setViaNodeOfRestriction(true);
p.setViaNodeOfRestriction(false);
}
p = replacement;
// replace point in way
points.set(i, p);
if (p.getHighwayCount() >= 2)
numNodesMerged++;
lastWayModified = true;
if (i + 1 < points.size() && points.get(i + 1) == p) {
points.remove(i);
anotherPassRequired = true;
}
if (i - 1 >= 0 && points.get(i - 1) == p) {
points.remove(i);
anotherPassRequired = true;
}
}
if (lastWayModified && mode == MODE_ROADS) {
modifiedRoads.put(way.getId(), cw);
}
}
if (extraPass) {
anotherPassRequired = false;
break;
} else {
if (!anotherPassRequired) {
// check if we have centres on different ways that overlap
for (CenterOfAngle coa : centers) {
if (coa.forceChange) {
anotherPassRequired = true;
extraPass = true;
break;
}
}
}
}
}
// finish: remove remaining duplicate points
int numWaysDeleted = 0;
lastWay = null;
boolean lastWayModified = false;
ConvertedWay lastConvertedWay = null;
for (ConvertedWay cw : convertedWays) {
if (cw.isOverlay())
continue;
Way way = cw.getWay();
List<Coord> points = way.getPoints();
if (points.size() < 2) {
if (log.isInfoEnabled())
log.info(" Way " + way.getTag("name") + " (" + way.toBrowseURL() + ") has less than 2 points - deleting it");
if (mode == MODE_LINES && waysThatMapToOnePoint.contains(way.getId()) == false)
log.warn("non-routable way ", way.getId(), "was removed");
if (mode == MODE_ROADS)
deletedRoads.add(way.getId());
++numWaysDeleted;
continue;
}
if (way.equals(lastWay)) {
if (lastWayModified) {
points.clear();
points.addAll(lastWay.getPoints());
if (cw.isReversed() != lastConvertedWay.isReversed())
Collections.reverse(points);
}
continue;
}
lastWay = way;
lastConvertedWay = cw;
lastWayModified = false;
Coord prev = points.get(points.size() - 1);
// loop backwards because we may delete points
for (int i = points.size() - 2; i >= 0; i--) {
Coord p = points.get(i);
if (p == prev) {
points.remove(i);
lastWayModified = true;
}
// if (p.equals(prev) && (p.getHighwayCount() < 2 || prev.getHighwayCount() < 2)){
// // not an error, but should not happen
// log.warn("way " + way.getId() + " still has consecutive equal points at " + p.toOSMURL());
// }
prev = p;
}
}
if (mode == MODE_ROADS) {
// points in roads
for (ConvertedWay cw : lines) {
if (!cw.isValid() || cw.isOverlay())
continue;
Way way = cw.getWay();
List<Coord> points = way.getPoints();
int n = points.size();
boolean hasReplacedPoints = false;
for (int i = 0; i < n; i++) {
Coord p = points.get(i);
if (p.isReplaced()) {
hasReplacedPoints = true;
points.set(i, getReplacement(p, null, replacements));
}
}
if (hasReplacedPoints && DEBUG_PATH != null) {
GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, way.getId() + "_mod_non_routable"), points);
}
}
for (RestrictionRelation rr : restrictions) {
for (Coord p : rr.getViaCoords()) {
Coord replacement = getReplacement(p, null, replacements);
if (p != replacement) {
rr.replaceViaCoord(p, replacement);
}
}
}
}
if (DEBUG_PATH != null) {
GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, (mode == MODE_ROADS ? "roads_" : "lines_") + "solved_badAngles"), bbox.toCoords(), new ArrayList<>(changedPlaces));
}
if (anotherPassRequired)
log.error("Removing wrong angles - didn't finish in " + pass + " passes, giving up!");
else
log.info("Removing wrong angles - finished in", pass, "passes (", numNodesMerged, "nodes merged,", numWaysDeleted, "ways deleted)");
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class WrongAngleFixer method calcMaxErrorDistance.
/**
* Calculate the rounding error tolerance for a given point.
* The latitude error may be max higher. Maybe this should be
* @param p0
* @return
*/
private static double calcMaxErrorDistance(Coord p0) {
Coord test = new Coord(p0.getLatitude(), p0.getLongitude() + 1);
double lonErr = p0.getDisplayedCoord().distance(test) / 2;
return lonErr;
}
Aggregations