Subversion Repositories mkgmap

Rev

Rev 3081 | 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.
 *
 *
 * Author: Steve Ratcliffe
 * Create date: Dec 1, 2007
 */

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.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapLine;

/**
 * This is a filter that smooths out lines at low resolutions. If the element
 * has no size at all at the given resolution, then it is not passed on down
 * the chain at all is excluded from the map at that resolution.
 *
 * @author Steve Ratcliffe
 */

public class SmoothingFilter implements MapFilter {

        private static final int MIN_SPACING = 5;

        private int shift;

        public void init(FilterConfig config) {
                this.shift = config.getShift();
        }

        /**
         * This applies to both lines and polygons.  We are going to smooth out
         * the points in the line so that you do not get jaggies.  We are assuming
         * that there is not an excess of points at the highest resolution.
         *
         * <ol>
         * <li>If there is just one point, the drop it.
         * <li>Ff the element is too small altogether, then drop it.
         * <li>If there are just two points the pass it on unchanged.  This is
         * probably a pretty common case.
         * <li>The first point goes in unchanged.
         * <li>Average points in groups so that they exceed the step size
         * at the shifted resolution.
         * </ol>
         *
         * @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.
         */

        public void doFilter(MapElement element, MapFilterChain next) {
                MapLine line = (MapLine) element;

                // First off we don't touch things if at the highest level of detail
                if (shift == 0) {
                        next.doFilter(element);
                        return;
                }

                // If the line is not very long then just let it through.  This is done
                // mainly for the background polygons.
                List<Coord> points = line.getPoints();
                int n = points.size();
                if (n <= 5) {
                        next.doFilter(element);
                        return;
                }

                // Create a new list to rewrite the points into.
                List<Coord> coords = new ArrayList<Coord>(n);

                // Get the step size, we want to place a point every time the
                // average exceeds this size.
                int stepsize = MIN_SPACING << shift;

                // Always add the first point
                Coord last = points.get(0);
                coords.add(last);

                // Average the rest
                Average av = new Average(last, stepsize);
                for (int i = 1; i < n; i++) {
                        Coord co = points.get(i);
                        av.add(co);

                        if (av.isMoreThanStep()) {
                                Coord nco = av.getAverageCoord();
                                coords.add(nco);
                                if (av.pointCounter()>1) i--;

                                last = nco;
                                av.reset(last);
                        }
                }

                Coord end = points.get(n - 1);
                if (!last.equals(end))
                        coords.add(end);

                MapLine newline = line.copy();

                newline.setPoints(coords);
                next.doFilter(newline);
        }

        /**
         * Class for averaging out points that are close together.
         */

        private static class Average {
                private int count;

                private int startLat;
                private int startLon;

                private int avlat;
                private int avlon;

                private int step;

                private final int stepsize;

                Average(Coord start, int stepsize) {
                        this.startLat = start.getLatitude();
                        this.startLon = start.getLongitude();
                        this.stepsize = stepsize;
                }

                public void add(int lat, int lon) {
                        count++;
                        this.avlat += lat;
                        this.avlon += lon;

                        step += Math.abs(startLat - lat);
                        step += Math.abs(startLon - lon);
                }

                public void reset(Coord start) {
                        this.startLat = start.getLatitude();
                        this.startLon = start.getLongitude();
                        step = 0;
                        count = 0;
                        avlat = 0;
                        avlon = 0;
                }

                public Coord getAverageCoord() {
                        assert count > 0;
                        return new Coord(avlat / count, avlon / count); // TODO high prec?
                }

                public void add(Coord co) {
                        add(co.getLatitude(), co.getLongitude());
                }

                public boolean isMoreThanStep() {
                        return (step > stepsize);
                }

                public int pointCounter() {
                        return count;
                }
        }
}