use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class ShapeMergeFilter method findCommonCoords.
/**
* Find the common Coord instances and save their positions for both shapes.
* @param s1 shape 1
* @param s2 shape 2
* @param s1PositionsToCheck will contain common positions in shape 1
* @param s2PositionsToCheck will contain common positions in shape 2
*/
private static void findCommonCoords(List<Coord> s1, List<Coord> s2, IntArrayList s1PositionsToCheck, IntArrayList s2PositionsToCheck) {
Map<Coord, Integer> s2PosMap = new IdentityHashMap<>(s2.size() - 1);
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);
s2PosMap.put(co, i);
}
int start = 0;
while (start < s1.size()) {
Coord co = s1.get(start);
if (!co.isPartOfShape2())
break;
start++;
}
int pos = start + 1;
int tested = 0;
while (true) {
if (pos + 1 >= s1.size())
pos = 0;
Coord co = s1.get(pos);
if (++tested >= s1.size())
break;
if (co.isPartOfShape2()) {
s1PositionsToCheck.add(pos);
Integer posInSh2 = s2PosMap.get(co);
assert posInSh2 != null;
s2PositionsToCheck.add(posInSh2);
}
pos++;
}
return;
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class ShapeMergeFilter method tryMerge.
/**
* Merge ShapeHelpers. Calls itself recursively.
* @param pattern a MapShape
* @param similarShapes {@link ShapeHelper} instances created from similar {@link MapShape}.
* This list is modified if shapes were merged.
*/
private void tryMerge(MapShape pattern, List<ShapeHelper> similarShapes) {
if (similarShapes.size() <= 1)
return;
List<ShapeHelper> noMerge = new ArrayList<>();
BitSet toMerge = new BitSet(similarShapes.size());
// abuse highway count to find identical points in different shapes
similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::resetHighwayCount));
similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::incHighwayCount));
// decrement counter for duplicated start/end node
similarShapes.forEach(sh -> sh.getPoints().get(0).decHighwayCount());
// points with count > 1 are probably shared by different shapes, collect the shapes
IdentityHashMap<Coord, BitSet> coord2Shape = new IdentityHashMap<>();
BitSet[] candidates = new BitSet[similarShapes.size()];
for (int i = 0; i < similarShapes.size(); i++) {
ShapeHelper sh0 = similarShapes.get(i);
List<Coord> sharedPoints = new ArrayList<>();
for (int j = 1; j < sh0.getPoints().size(); j++) {
Coord c = sh0.getPoints().get(j);
if (c.getHighwayCount() > 1) {
sharedPoints.add(c);
}
}
if (sharedPoints.size() == 0 || sh0.getPoints().size() - sharedPoints.size() > PolygonSplitterFilter.MAX_POINT_IN_ELEMENT) {
// merge will not work
noMerge.add(sh0);
continue;
}
assert candidates[i] == null;
candidates[i] = new BitSet();
BitSet curr = candidates[i];
curr.set(i);
toMerge.set(i);
for (Coord c : sharedPoints) {
BitSet set = coord2Shape.get(c);
if (set == null) {
set = new BitSet();
coord2Shape.put(c, set);
} else {
for (int j = set.nextSetBit(0); j >= 0; j = set.nextSetBit(j + 1)) {
candidates[j].set(i);
}
curr.or(set);
}
set.set(i);
}
}
if (coord2Shape.isEmpty()) {
// nothing to do
return;
}
List<ShapeHelper> next = new ArrayList<>();
boolean merged = false;
BitSet done = new BitSet();
BitSet delayed = new BitSet();
for (int i = toMerge.nextSetBit(0); i >= 0; i = toMerge.nextSetBit(i + 1)) {
if (done.get(i))
continue;
BitSet all = candidates[i];
if (all.cardinality() <= 1) {
if (!all.isEmpty())
delayed.set(i);
continue;
}
all.andNot(done);
if (all.isEmpty())
continue;
List<ShapeHelper> result = new ArrayList<>();
for (int j = all.nextSetBit(0); j >= 0; j = all.nextSetBit(j + 1)) {
ShapeHelper sh = similarShapes.get(j);
int oldSize = result.size();
result = addWithConnectedHoles(result, sh, pattern.getType());
if (result.size() < oldSize + 1) {
merged = true;
log.debug("shape with id", sh.id, "was merged", (oldSize + 1 - result.size()), " time(s) at resolution", resolution);
}
}
// XXX : not exact, there may be other combinations of shapes which can be merged
// e.g. merge of shape 1 + 2 may not work but 2 and 3 could still be candidates.
done.or(all);
next.addAll(result);
}
delayed.andNot(done);
if (!delayed.isEmpty()) {
for (int i = delayed.nextSetBit(0); i >= 0; i = delayed.nextSetBit(i + 1)) {
noMerge.add(similarShapes.get(i));
}
}
similarShapes.clear();
similarShapes.addAll(noMerge);
if (merged)
tryMerge(pattern, next);
// Maybe add final step which calls addWithConnectedHoles for all remaining shapes
// this will find a few more merges but is still slow for maps with lots of islands
similarShapes.addAll(next);
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class ShapeMergeFilter method tryMerge.
/**
* Find out if two shapes have common points. If yes, merge them.
* @param sh1 1st shape1
* @param sh2 2st shape2
* @return merged shape or 1st shape if no common point found or {@code dupShape}
* if both shapes describe the same area.
*/
private ShapeHelper tryMerge(ShapeHelper sh1, ShapeHelper sh2) {
// both clockwise or both ccw ?
boolean sameDir = sh1.areaTestVal > 0 && sh2.areaTestVal > 0 || sh1.areaTestVal < 0 && sh2.areaTestVal < 0;
List<Coord> points1, points2;
if (sh2.getPoints().size() > sh1.getPoints().size()) {
points1 = sh2.getPoints();
points2 = sh1.getPoints();
} else {
points1 = sh1.getPoints();
points2 = sh2.getPoints();
}
// find all coords that are common in the two shapes
IntArrayList sh1PositionsToCheck = new IntArrayList();
IntArrayList sh2PositionsToCheck = new IntArrayList();
findCommonCoords(points1, points2, sh1PositionsToCheck, sh2PositionsToCheck);
if (sh1PositionsToCheck.isEmpty()) {
return sh1;
}
if (sh2PositionsToCheck.size() + 1 >= points2.size()) {
// or a piece that fills a hole
if (points1.size() == points2.size() && Math.abs(sh1.areaTestVal) == Math.abs(sh2.areaTestVal)) {
// XXX this might fail if one of the shapes is self intersecting
return DUP_SHAPE;
}
}
List<Coord> merged = null;
if (points1.size() + points2.size() - 2 * sh1PositionsToCheck.size() < PolygonSplitterFilter.MAX_POINT_IN_ELEMENT) {
merged = mergeLongestSequence(points1, points2, sh1PositionsToCheck, sh2PositionsToCheck, sameDir);
if (merged.isEmpty())
return DUP_SHAPE;
if (merged.get(0) != merged.get(merged.size() - 1))
merged = null;
else if (merged.size() > PolygonSplitterFilter.MAX_POINT_IN_ELEMENT) {
// don't merge because merged polygon would be split again
log.info("merge rejected: merged shape has too many points " + merged.size());
merged = null;
}
}
ShapeHelper shm = null;
if (merged != null) {
shm = new ShapeHelper(merged);
if (Math.abs(shm.areaTestVal) != Math.abs(sh1.areaTestVal) + Math.abs(sh2.areaTestVal)) {
log.warn("merging shapes skipped for shapes near", points1.get(sh1PositionsToCheck.getInt(0)).toOSMURL(), "(maybe overlapping shapes?)");
merged = null;
shm = null;
} else {
if (log.isInfoEnabled()) {
log.info("merge of shapes near", points1.get(sh1PositionsToCheck.getInt(0)).toOSMURL(), "reduces number of points from", (points1.size() + points2.size()), "to", merged.size());
}
}
}
if (shm != null)
return shm;
if (merged == null)
return sh1;
return null;
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class ShapeMergeFilter method mergeSimilar.
/**
* Merge similar shapes.
* @param similar list of similar shapes
* @param mergedShapes list to which the shapes are added
*/
private void mergeSimilar(List<MapShape> similar, List<MapShape> mergedShapes) {
if (similar.size() == 1) {
mergedShapes.addAll(similar);
return;
}
List<ShapeHelper> list = new ArrayList<>();
MapShape s1 = similar.get(0);
for (MapShape ms : similar) {
ShapeHelper sh = new ShapeHelper(ms.getPoints());
sh.id = ms.getOsmid();
list.add(sh);
}
tryMerge(s1, list);
for (ShapeHelper sh : list) {
MapShape newShape = s1.copy();
assert sh.getPoints().get(0) == sh.getPoints().get(sh.getPoints().size() - 1);
if (sh.id == 0) {
// this shape is the result of a merge
List<Coord> optimizedPoints = WrongAngleFixer.fixAnglesInShape(sh.getPoints());
if (optimizedPoints.isEmpty())
continue;
newShape.setPoints(optimizedPoints);
newShape.setOsmid(FakeIdGenerator.makeFakeId());
} else {
newShape.setPoints(sh.getPoints());
newShape.setOsmid(sh.id);
}
mergedShapes.add(newShape);
}
}
use of uk.me.parabola.imgfmt.app.Coord in project mkgmap by openstreetmap.
the class ShapeMergeFilter method calcAreaSizeTestVal.
/**
* Calculate the high precision area size test value.
* @param points
* @return area size in high precision map units * 2.
* The value is >= 0 if the shape is clockwise, else < 0
*/
public static long calcAreaSizeTestVal(List<Coord> points) {
if (points.size() < 4)
// straight line cannot enclose an area
return 0;
if (points.get(0).highPrecEquals(points.get(points.size() - 1)) == false) {
log.error("shape is not closed");
return 0;
}
Iterator<Coord> polyIter = points.iterator();
Coord c2 = polyIter.next();
long signedAreaSize = 0;
while (polyIter.hasNext()) {
Coord c1 = c2;
c2 = polyIter.next();
signedAreaSize += (long) (c2.getHighPrecLon() + c1.getHighPrecLon()) * (c1.getHighPrecLat() - c2.getHighPrecLat());
}
if (Math.abs(signedAreaSize) < SINGLE_POINT_AREA) {
log.debug("very small shape near", points.get(0).toOSMURL(), "signed area in high prec map units:", signedAreaSize);
}
return signedAreaSize;
}
Aggregations