Subversion Repositories mkgmap

Rev

Rev 4786 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package uk.me.parabola.mkgmap.filters;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.util.MultiHashMap;



public class LineMergeFilter{
        private static final Logger log = Logger.getLogger(LineMergeFilter.class);

        private List<MapLine> linesMerged;
        private final MultiHashMap<Coord, MapLine> sharedPoints = new MultiHashMap<>();

        private void addLine(MapLine line) {
                linesMerged.add(line);
                List<Coord> points = line.getPoints();
                Coord start = points.get(0);
                Coord end = points.get(points.size() - 1);
                sharedPoints.add(start, line);
                sharedPoints.add(end, line);
        }

        private void addPointsAtStart(MapLine line, List<Coord> additionalPoints) {
                log.info("merged lines before " + line.getName());
                List<Coord> points = line.getPoints();
                sharedPoints.removeMapping(points.get(0), line);
                line.insertPointsAtStart(additionalPoints);
                sharedPoints.add(points.get(0), line);
        }

        private void addPointsAtEnd(MapLine line, List<Coord> additionalPoints) {
                log.info("merged lines after " + line.getName());
                List<Coord> points = line.getPoints();
                sharedPoints.removeMapping(points.get(points.size() - 1), line);
                line.insertPointsAtEnd(additionalPoints);
                sharedPoints.add(points.get(points.size() - 1), line);
        }

        /**
         * Filter lines which are not visible at the given resolution, merge them if type and name are equal.
         * @param lines the lines to merge
         * @param res the resolution
         * @param mergeRoads set to true if roads can be merged, too
         * @param reverseAllowed set to true if lines without direction can be reversed
         * @return list of visible lines, merged as much as possible
         */

        public List<MapLine> merge(List<MapLine> lines, int res, boolean mergeRoads, boolean reverseAllowed) {
                linesMerged = new ArrayList<>();
               
                for (MapLine line : lines) {
                        if (line.getMinResolution() > res || line.getMaxResolution() < res)
                                continue;
                        if (line.isRoad() && !mergeRoads) {
                                linesMerged.add(line);
                                continue;
                        }
                       
                        if (!tryMerge(line, reverseAllowed)) {
                                // No matching, create a copy of line
                                MapLine l = line.copy();
                                l.setPoints(new ArrayList<>(line.getPoints()));                        
                                addLine(l);
                        }
                }
                return linesMerged;
        }

        /**
         * Try to find previously processed lines to which the points of this line can be added.
         * @param line the new line to add
         * @param reverseAllowed set to true if lines without direction can be reversed
         * @return true if points were merged
         */

        private boolean tryMerge(MapLine line, boolean reverseAllowed) {
                Coord start = line.getPoints().get(0);
                Coord end = line.getPoints().get(line.getPoints().size() - 1);
               
                Set<MapLine> candidates = new LinkedHashSet<>(sharedPoints.get(start));
                candidates.addAll(sharedPoints.get(end));
                Coord mergePoint = null;
                MapLine mergedLine = null;
                for (MapLine line2 : candidates) {
                        mergePoint = tryCandidate(line, line2, reverseAllowed);
                        if (mergePoint != null) {
                                mergedLine = line2;
                                break;
                        }
                }
                if (mergePoint == null)
                        return false;

                // check if the other end of the new line could also be merged
                Coord otherEnd = mergePoint.equals(end) ? start : end;
                candidates.clear();
                candidates.addAll(sharedPoints.get(otherEnd));
                for (MapLine line2 : candidates) {
                        mergePoint = tryCandidate(line2, mergedLine, reverseAllowed);
                        if (mergePoint != null) {
                                sharedPoints.removeMapping(line2.getPoints().get(0), line2);
                                sharedPoints.removeMapping(line2.getPoints().get(line2.getPoints().size()-1), line2);
                                linesMerged.remove(line2);
                                break;
                        }
                }
                return true;
        }

        /**
         * Merge if lines are similar and have exactly one common end node. May create loops.
         *
         * @param line1  line to merge
         * @param line2 line to keep
         * @param reverseAllowed set to true if lines without direction can be reversed
         * @return the common point at which the lines were merged, taken from line1
         */

        private Coord tryCandidate(MapLine line1, MapLine line2, boolean reverseAllowed) {
                if (line1 == line2 || !isSimilar(line1, line2))
                        return null;
                List<Coord> points1 = line1.getPoints();
                Coord start1 = points1.get(0);
                Coord end1 = points1.get(points1.size() - 1);
                List<Coord> points2 = line2.getPoints();
                Coord start2 = points2.get(0);
                Coord end2 = points2.get(points2.size() - 1);
               
                if (end2.equals(start1)) {
                        addPointsAtEnd(line2, line1.getPoints());
                        return start1;
                } else if (start2.equals(end1)) {
                        addPointsAtStart(line2, line1.getPoints());
                        return end1;
                } else if (reverseAllowed && end2.equals(end1) && !line1.isDirection()) {
                        ArrayList<Coord> reversed = new ArrayList<>(line1.getPoints());
                        Collections.reverse(reversed);
                        addPointsAtEnd(line2, reversed);
                        return end1;
                } else if (reverseAllowed && start2.equals(start1) && !line1.isDirection()) {
                        ArrayList<Coord> reversed = new ArrayList<>(line1.getPoints());
                        Collections.reverse(reversed);
                        addPointsAtStart(line2, reversed);
                        return start1;
                }
                return null;
        }

        private static boolean isSimilar(MapLine l1, MapLine l2) {
                return l1.getType() == l2.getType() && l1.isDirection() == l2.isDirection()
                                && Objects.equals(l1.getName(), l2.getName());
        }
}