/* * 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; } } }