Subversion Repositories mkgmap

Rev

Rev 2436 | View as "text/plain" | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2006, 2012.
 *
 * 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.util;

import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.reader.osm.Way;

/**
 * This is a tool class that provides static methods to convert between mkgmap
 * objects and Java2D objects. The Java2D objects provide some optimized polygon
 * algorithms that are quite useful so that it makes sense to perform the
 * conversion.
 *
 * @author WanMil
 */

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

        /**
         * Creates a Java2D {@link Area} object from the given mkgmap rectangular
         * {@link uk.me.parabola.imgfmt.app.Area} object.
         *
         * @param bbox a rectangular bounding box
         * @return the converted Java2D area
         */

        public static Area createBoundsArea(uk.me.parabola.imgfmt.app.Area bbox) {
                return new Area(new Rectangle(bbox.getMinLong(), bbox.getMinLat(),
                                bbox.getMaxLong() - bbox.getMinLong(), bbox.getMaxLat()
                                                - bbox.getMinLat()));
        }

        /**
         * Converts the bounding box of a Java2D {@link Area} object to an mkgmap
         * {@link uk.me.parabola.imgfmt.app.Area} object.
         *
         * @param area a Java2D area
         * @return the bounding box
         */

        public static uk.me.parabola.imgfmt.app.Area createBbox(Area area) {
                Rectangle areaBounds = area.getBounds();
                return new uk.me.parabola.imgfmt.app.Area(areaBounds.y, areaBounds.x,
                                areaBounds.y + areaBounds.height, areaBounds.x
                                                + areaBounds.width);
        }

        /**
         * Creates a Java2D {@link Area} object from a polygon given as a list of
         * {@link Coord} objects. This list should describe a closed polygon.
         *
         * @param polygonPoints a list of points that describe a closed polygon
         * @return the converted Java2D area
         */

        public static Area createArea(List<Coord> polygonPoints) {
                return new Area(createPolygon(polygonPoints));
        }

        /**
         * Create a polygon from a list of points.
         *
         * @param points list of points
         * @return the polygon
         */

        public static Polygon createPolygon(List<Coord> points) {
                Polygon polygon = new Polygon();
                for (Coord co : points) {
                        polygon.addPoint(co.getLongitude(), co.getLatitude());
                }
                return polygon;
        }

        /**
         * Convert an area that may contains multiple areas to a list of singular
         * areas
         *
         * @param area an area
         * @return list of singular areas
         */

        public static List<Area> areaToSingularAreas(Area area) {
                if (area.isEmpty()) {
                        return Collections.emptyList();
                } else if (area.isSingular()) {
                        return Collections.singletonList(area);
                } else {
                        List<Area> singularAreas = new ArrayList<Area>();

                        // all ways in the area MUST define outer areas
                        // it is not possible that one of the areas define an inner segment

                        float[] res = new float[6];
                        PathIterator pit = area.getPathIterator(null);
                        int prevLat = Integer.MIN_VALUE;
                        int prevLong = Integer.MIN_VALUE;

                        Polygon p = null;
                        while (!pit.isDone()) {
                                int type = pit.currentSegment(res);
                                int lat = Math.round(res[1]);
                                int lon = Math.round(res[0]);

                                switch (type) {
                                case PathIterator.SEG_LINETO:
                                        if (prevLat != lat || prevLong != lon) {
                                                p.addPoint(lon, lat);
                                        }
                                        prevLat = lat;
                                        prevLong = lon;
                                        break;
                                case PathIterator.SEG_CLOSE:
                                        p.addPoint(p.xpoints[0], p.ypoints[0]);
                                        Area a = new Area(p);
                                        if (!a.isEmpty()) {
                                                singularAreas.add(a);
                                        }
                                        p = null;
                                        break;
                                case PathIterator.SEG_MOVETO:
                                        if (p != null) {
                                                Area a2 = new Area(p);
                                                if (!a2.isEmpty()) {
                                                        singularAreas.add(a2);
                                                }
                                        }
                                        p = new Polygon();
                                        p.addPoint(lon, lat);
                                        break;
                                default:
                                        log.error("Unsupported path iterator type " + type
                                                        + ". This is an mkgmap error.");
                                }

                                prevLat = lat;
                                prevLong = lon;

                                pit.next();
                        }
                        return singularAreas;
                }
        }

        /**
         * Convert an area to an mkgmap way. The caller must ensure that the area is
         * singular. Otherwise only the first part of the area is converted.
         *
         * @param area the area
         * @return a new mkgmap way
         */

        public static List<Coord> singularAreaToPoints(Area area) {
                if (area.isEmpty()) {
                        return null;
                }

                List<Coord> points = null;

                float[] res = new float[6];
                PathIterator pit = area.getPathIterator(null);
                int prevLat = Integer.MIN_VALUE;
                int prevLong = Integer.MIN_VALUE;

                while (!pit.isDone()) {
                        int type = pit.currentSegment(res);

                        int lat = Math.round(res[1]);
                        int lon = Math.round(res[0]);

                        switch (type) {
                        case PathIterator.SEG_MOVETO:
                                points = new ArrayList<Coord>();
                                points.add(new Coord(lat, lon));
                                break;
                        case PathIterator.SEG_LINETO:
                                assert points != null;
                                if (prevLat != lat || prevLong != lon) {
                                        points.add(new Coord(lat, lon));
                                }
                                break;
                        case PathIterator.SEG_CLOSE:
                                assert points != null;
                                if (points.get(0).equals(points.get(points.size() - 1)) == false)
                                        points.add(points.get(0));
                                return points;
                        default:
                                log.error("Unsupported path iterator type " + type
                                                + ". This is an mkgmap error.");
                        }

                        prevLat = lat;
                        prevLong = lon;

                        pit.next();
                }
                return points;
        }

        /**
         * Convert the area back into a list of polygons each represented by a list
         * of coords. It is possible that the area contains multiple discontiguous
         * polygons, so you may append more than one shape to the output list.<br/>
         * <b>Attention:</b> The outline of the polygon is has clockwise order whereas
         * holes in the polygon have counterclockwise order.
         *
         * @param area The area to be converted.
         * @return a list of closed polygons
         */

        public static List<List<Coord>> areaToShapes(java.awt.geom.Area area) {
                List<List<Coord>> outputs = new ArrayList<List<Coord>>(4);

                float[] res = new float[6];
                PathIterator pit = area.getPathIterator(null);
               
                // store float precision coords to check if the direction (cw/ccw)
                // of a polygon changes due to conversion to int precision
                List<Float> floatLat = null;
                List<Float>     floatLon = null;

                List<Coord> coords = null;

                int iPrevLat = Integer.MIN_VALUE;
                int iPrevLong = Integer.MIN_VALUE;

                while (!pit.isDone()) {
                        int type = pit.currentSegment(res);

                        float fLat = res[1];
                        float fLon = res[0];
                        int iLat = Math.round(fLat);
                        int iLon = Math.round(fLon);
                       
                        switch (type) {
                        case PathIterator.SEG_LINETO:
                                floatLat.add(fLat);
                                floatLon.add(fLon);

                                if (iPrevLat != iLat || iPrevLong != iLon)
                                        coords.add(new Coord(iLat,iLon));

                                iPrevLat = iLat;
                                iPrevLong = iLon;
                                break;
                        case PathIterator.SEG_MOVETO:
                        case PathIterator.SEG_CLOSE:
                                if ((type == PathIterator.SEG_MOVETO && coords != null) || type == PathIterator.SEG_CLOSE) {
                                        if (coords.size() > 2 && coords.get(0).equals(coords.get(coords.size() - 1)) == false) {
                                                coords.add(coords.get(0));
                                        }
                                        if (coords.size() > 3){
                                                // use float values to verify area size calculations with higher precision
                                                if (floatLat.size() > 2) {
                                                        if (floatLat.get(0).equals(floatLat.get(floatLat.size() - 1)) == false
                                                                        || floatLon.get(0).equals(floatLon.get(floatLon.size() - 1)) == false){
                                                                floatLat.add(floatLat.get(0));
                                                                floatLon.add(floatLon.get(0));
                                                        }
                                                }

                                                // calculate area size with float values
                                                double realAreaSize = 0;
                                                float pf1Lat = floatLat.get(0);
                                                float pf1Lon = floatLon.get(0);
                                                for(int i = 1; i < floatLat.size(); i++) {
                                                        float pf2Lat = floatLat.get(i);
                                                        float pf2Lon = floatLon.get(i);
                                                        realAreaSize += ((double)pf1Lon * pf2Lat -
                                                                        (double)pf2Lon * pf1Lat);
                                                        pf1Lat = pf2Lat;
                                                        pf1Lon = pf2Lon;
                                                }
                                               
                                       
                                                // Check if the polygon with float precision has the same direction
                                                // than the polygon with int precision. If not reverse the int precision
                                                // polygon. Its direction has changed artificially by the int conversion.
                                                boolean floatPrecClockwise = (realAreaSize <= 0);
                                                if (Way.clockwise(coords) != floatPrecClockwise) {
                                                       
                                                        if (log.isInfoEnabled()) {
                                                                log.info("Converting area to int precision changes direction. Will correct that.");
                                                                StringBuilder sb = new StringBuilder("[");
                                                                for (int i = 0; i < floatLat.size(); i++) {
                                                                        if (i > 0) {
                                                                                sb.append(", ");
                                                                        }
                                                                        sb.append(floatLat.get(i));
                                                                        sb.append("/");
                                                                        sb.append(floatLon.get(i));
                                                                }
                                                                sb.append("]");
                                                                log.info("Float area: ", sb);
                                                                log.info("Int area: ", coords);
                                                        }
                                                       
                                                        Collections.reverse(coords);
                                                }
                                                outputs.add(coords);
                                        }
                                }
                                if (type == PathIterator.SEG_MOVETO){
                                        floatLat= new ArrayList<Float>();
                                        floatLon= new ArrayList<Float>();
                                        floatLat.add(fLat);
                                        floatLon.add(fLon);
                                        coords = new ArrayList<Coord>();
                                        coords.add(new Coord(iLat,iLon));
                                        iPrevLat = iLat;
                                        iPrevLong = iLon;
                                } else {
                                        floatLat= null;
                                        floatLon= null;
                                        coords = null;
                                        iPrevLat = Integer.MIN_VALUE;
                                        iPrevLong = Integer.MIN_VALUE;
                                }
                                break;
                        default:
                                log.error("Unsupported path iterator type " + type
                                                + ". This is an mkgmap error.");
                        }

                        pit.next();
                }

                return outputs;
        }
}