Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2007 Steve Ratcliffe
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.filters;

import java.util.ArrayList;
import java.util.List;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.general.MapRoad;
import uk.me.parabola.mkgmap.reader.osm.GType;

public class RoundCoordsFilter implements MapFilter {

        private int shift;
        private boolean keepNodes;
        private int level;

        @Override
        public void init(FilterConfig config) {
                shift = config.getShift();
                level = config.getLevel();
                keepNodes = level == 0 && config.hasNet();
        }

        /**
         * @param element A map element that will be a line or a polygon.
         * @param next This is used to pass the possibly transformed element onward.
         */

        @Override
        public void doFilter(MapElement element, MapFilterChain next) {
                if (shift == 0) {
                        // do nothing
                        next.doFilter(element);
                } else {
                        MapLine line = (MapLine) element;
                        int full = 1 << shift;
                        int half = 1 << (shift - 1);    // 0.5 shifted
                        int mask = ~((1 << shift) - 1); // to remove fraction bits
                       
                        // round lat/lon values to nearest for shift
                        List<Coord> newPoints = new ArrayList<>(line.getPoints().size());

                        List<Coord> coords = line.getPoints();
                        int endIndex = coords.size() -1;

                        Coord lastP = null;
                        boolean hasNumbers = keepNodes && line.isRoad() && ((MapRoad) line).getRoadDef().hasHouseNumbers();
                        boolean isContourLine = GType.isContourLine(line);
                        for(int i = 0; i <= endIndex; i++) {
                                Coord p = coords.get(i);
                                if (!keepNodes && p.isAddedNumberNode()) {
                                        // ignore nodes added by housenumber processing for levels > 0  
                                        continue;
                                }

                                final boolean keepNumberNode = hasNumbers && p.isNumberNode();
                                Coord newP;
                                if (p instanceof CoordNode && keepNodes) {
                                        int lat = (p.getLatitude() + half) & mask;
                                        int lon = (p.getLongitude() + half) & mask;
                                        newP = new CoordNode(lat, lon, p.getId(), p.getOnBoundary(), p.getOnCountryBorder());
                                        newP.preserved(true);
                                } else if (!isContourLine || i == 0 || i == endIndex) {
                                        int lat = (p.getLatitude() + half) & mask;
                                        int lon = (p.getLongitude() + half) & mask;
                                        newP = new Coord(lat, lon);

                                        newP.setNumberNode(keepNumberNode);
                                        newP.preserved(p.preserved() || keepNumberNode);                                       
                                } else { // find best match, used only with contour lines so far
                                        Coord a = coords.get(i -1);
                                        Coord b = coords.get(i +1);

                                        // point 0,0
                                        int lat = p.getLatitude() & mask;
                                        int lon = p.getLongitude() & mask;
                                        newP = new Coord(lat, lon);
                                        double minDistortion = calcDistortion(newP, a, p, b);

                                        Coord testP;
                                        double testDistortion;

                                        // point 0,1
                                        lon = (p.getLongitude() + full) & mask;
                                        testP = new Coord(lat, lon);
                                        testDistortion = calcDistortion(testP, a, p, b);
                                        if (testDistortion < minDistortion) {
                                                minDistortion = testDistortion;
                                                newP = testP;
                                        }

                                        // point 1,1
                                        lat = (p.getLatitude() + full) & mask;
                                        testP = new Coord(lat, lon);
                                        testDistortion = calcDistortion(testP, a, p, b);
                                        if (testDistortion < minDistortion) {
                                                minDistortion = testDistortion;
                                                newP = testP;
                                        }

                                        // point 1,0
                                        lon = p.getLongitude() & mask;
                                        testP = new Coord(lat, lon);
                                        testDistortion = calcDistortion(testP, a, p, b);
                                        if (testDistortion < minDistortion) {
                                                newP = testP;
                                        }

                                        newP.setNumberNode(keepNumberNode);
                                        newP.preserved(p.preserved() || keepNumberNode);                                       
                                }
                               
                                // only add the new point if it has different
                                // coordinates to the last point or if it's a
                                // special node
                                if (lastP == null || !lastP.equals(newP) || newP.getId() > 0 || (hasNumbers && newP.isNumberNode())) {
                                        newPoints.add(newP);
                                        lastP = newP;
                                } else if (newP.preserved()) {
                                        // this point is not going to be used because it
                                        // has the same (rounded) coordinates as the last
                                        // node but it has been marked as being "preserved" -
                                        // transfer that property to the previous point so
                                        // that it's not lost in further filters
                                        lastP.preserved(true);
                                }
                        }
                        if (newPoints.size() > 1) {
                                MapLine newLine = line.copy();
                                newLine.setPoints(newPoints);
                                next.doFilter(newLine);
                        }
                }
        }

        /**
         * Calculation a value that measures the distortion caused by rounding.
         *
         * @param roundedMid the place where the rounded mid would be
         * @param before     the exact position of the point before mid
         * @param mid        the exact position of the middle point
         * @param after      the exact position of the point after mid
         * @return a value that measures the distortion, lower value means less
         *         distortion
         */

        private static double calcDistortion(Coord roundedMid, Coord before, Coord mid, Coord after) {
                // distances are a simple measure
                return roundedMid.shortestDistToLineSegment(before, mid) + roundedMid.shortestDistToLineSegment(mid, after);
        }
}