/*
* Copyright (C) 2013.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* 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.Collections;
import java.util.List;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.trergn.LinePreparer;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.general.MapShape;
/**
* This filter does more or less the same calculations as LinePreparer.calcDeltas
* It rejects lines that have not enough different points, and it optimises
* shapes so that they require fewer bits in the img file.
* @author GerdP
*
*/
public class LinePreparerFilter implements MapFilter {
private int shift;
private final Subdivision subdiv;
public LinePreparerFilter(Subdivision subdiv) {
this.subdiv = subdiv;
}
public void init(FilterConfig config) {
shift = config.getShift();
}
/**
* @param element A map element that will be a line or a polygon.
* @param next This is used to pass the element onward.
*/
public void doFilter(MapElement element, MapFilterChain next) {
MapLine line = (MapLine) element;
int numPoints = line.getPoints().size();
boolean first = true;
int minPointsRequired = (element instanceof MapShape) ? 3:2;
if (minPointsRequired == 3 && line.getPoints().get(0).equals(line.getPoints().get(numPoints-1)))
++minPointsRequired;
int lastLat = 0;
int lastLong = 0;
int numPointsEncoded = 1;
// fields to keep track of the largest delta values
int[] maxBits = {0,0};
int[] maxBits2nd = {0,0};
int[] maxBitsPos = {0,0};
for (int i = 0; i < numPoints; i++) {
Coord co = line.getPoints().get(i);
int lat = subdiv.roundLatToLocalShifted(co.getLatitude());
int lon = subdiv.roundLonToLocalShifted(co.getLongitude());
if (first) {
lastLat = lat;
lastLong = lon;
first = false;
continue;
}
// compute normalized differences
// -2^(shift-1) <= dx, dy < 2^(shift-1)
// XXX: relies on the fact that java integers are 32 bit signed
final int offset = 8+shift;
int dx = (lon - lastLong) << offset >> offset;
int dy = (lat - lastLat) << offset >> offset;
lastLong = lon;
lastLat = lat;
if (dx == 0 && dy == 0){
if(!line.isRoad() || (co.getId() == 0 && co.isNumberNode() == false))
continue;
}
++numPointsEncoded;
if (numPointsEncoded >= minPointsRequired && element instanceof MapShape == false)
break;
// find out largest and 2nd largest delta for both dx and dy
for (int k = 0; k < 2; k++){
int nBits = LinePreparer.bitsNeeded((k==0) ? dx:dy);
if (nBits > maxBits2nd[k]){
if (nBits > maxBits[k]){
maxBits2nd[k] = maxBits[k];
maxBits[k] = nBits;
maxBitsPos[k] = i;
}
else
maxBits2nd[k] = nBits;
}
}
}
if(numPointsEncoded < minPointsRequired)
return;
if (minPointsRequired >= 3){
// check if we can optimise shape by rotating
// so that the line segment that requires the highest number of bits
// is not encoded and thus fewer bits
// are required for all points
// TODO: maybe add additional points to further reduce max. delta values
// or reverse order if largest delta is negative
int maxReduction = 0;
int rotation = 0;
for (int k = 0; k < 2; k++){
int delta = maxBits[k] - maxBits2nd[k];
// prefer largest delta, then smallest rotation
if (delta > maxReduction || delta == maxReduction && rotation > maxBitsPos[k]){
maxReduction = delta;
rotation = maxBitsPos[k];
}
}
/*
int savedBits = (numPoints-1 * maxReduction);
if (savedBits > 100){
System.out.println("rotation of shape saves " + savedBits + " bits");
}
*/
if (rotation != 0){
List<Coord> points = line.getPoints();
if (minPointsRequired == 4)
points.remove(numPoints-1);
Collections.rotate(points, -rotation);
if (minPointsRequired == 4)
points.add(points.get(0));
}
}
next.doFilter(element);
}
}