Subversion Repositories mkgmap

Rev

Rev 4567 | 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.Shape;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.openstreetmap.osmosis.core.filter.common.PolygonFileReader;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;

/**
 * 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 createArea(bbox.toCoords());
        }

        /**
         * Converts the bounding box of a Java2D {@link Shape} object to an mkgmap
         * {@link uk.me.parabola.imgfmt.app.Area} object.
         *
         * @param shape a Java2D Shape (Area, Path2D, ...)
         * @return the bounding box
         */

        public static uk.me.parabola.imgfmt.app.Area createBbox(Shape shape) {
                Rectangle areaBounds = shape.getBounds();
                return new uk.me.parabola.imgfmt.app.Area(areaBounds.y,areaBounds.x,(int) areaBounds.getMaxY(),(int) areaBounds.getMaxX());
        }
       

        /**
         * 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(createPath2D(polygonPoints));
        }
       
        /**
         * Creates a Java2D {@link Path2D} 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 path
         */

        public static Path2D createPath2D (List<Coord> polygonPoints) {
                int n = polygonPoints.size();
                if (n < 3)
                        return new Path2D.Double();
               
                Path2D path = new Path2D.Double(PathIterator.WIND_NON_ZERO, n);
                if (polygonPoints.get(0).highPrecEquals(polygonPoints.get(n-1))){
                        // if first and last point are high-prec-equal, ignore last point
                        // because we use closePath() to signal that
                        --n;
                }
                int lastLat = Integer.MAX_VALUE,lastLon = Integer.MAX_VALUE;
                for (int i = 0; i < n; i++){
                        Coord co = polygonPoints.get(i);
                        int latHp = co.getHighPrecLat();
                        int lonHp = co.getHighPrecLon();
                        double x = (double)lonHp / (1<<Coord.DELTA_SHIFT);
                        double y = (double)latHp / (1<<Coord.DELTA_SHIFT);
                        if (i == 0)
                                path.moveTo(x, y);
                        else {
                                if (lastLon != lonHp || lastLat != latHp)
                                        path.lineTo(x, y);
                        }
                        lastLon = lonHp;
                        lastLat = latHp;
                }
                path.closePath();
                return path;
        }

        public static Polygon createHighPrecPolygon(List<Coord> points) {
                Polygon polygon = new Polygon();
                for (Coord co : points) {
                        polygon.addPoint(co.getHighPrecLon(), co.getHighPrecLat());
                }
                return polygon;
        }

        public static List<Area> areaToSingularAreas(Area area) {
                return areaToSingularAreas(0, area);
        }
        /**
         * Convert an area that may contains multiple areas to a list of singular
         * areas keeping the highest possible precision.
         *
         * @param area an area
         * @return list of singular areas
         */

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

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

                        double[] res = new double[6];
                        PathIterator pit = area.getPathIterator(null);
                        Path2D path = null;
                        while (!pit.isDone()) {
                                int type = pit.currentSegment(res);
                                double lat = res[1];
                                double lon = res[0];

                                switch (type) {
                                case PathIterator.SEG_LINETO:
                                        path.lineTo(lon, lat);
                                        break;
                                case PathIterator.SEG_CLOSE:
                                        path.closePath();
                                        Area a = new Area(path);
                                        if (!a.isEmpty()) {
                                                if (depth < 10 && !a.isSingular()){
                                                        // should not happen, but it does. Error in Area code?
                                                        singularAreas.addAll(areaToSingularAreas(depth+1,a));
                                                }
                                                else
                                                        singularAreas.add(a);
                                        }
                                        path = null;
                                        break;
                                case PathIterator.SEG_MOVETO:
                                        path = new Path2D.Double();
                                        path.moveTo(lon, lat);
                                        break;
                                default:
                                        log.error("Unsupported path iterator type " + type
                                                        + ". This is an mkgmap error.");
                                }
                                pit.next();
                        }
                        return singularAreas;
                }
        }

        /**
         * Convert an area to an mkgmap way. The caller must ensure that the area is
         * singular. Otherwise only the first non-empty part of the area is converted.
         *
         * @param area the area
         * @return list of points
         */

        public static List<Coord> singularAreaToPoints(Area area) {
                return singularAreaToPoints(area, null);
        }
       
        /**
         * Convert an area to an mkgmap way. The caller must ensure that the area is
         * singular. Otherwise only the first non-empty part of the area is converted.
         *
         * @param area the area
         * @param coordPool the map for unique Coord instances, can be null
         * @return list of points
         */

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

                List<Coord> points = null;

                double[] res = new double[6];
                PathIterator pit = area.getPathIterator(null);
                int prevLatHp = Integer.MIN_VALUE;
                int prevLongHp = Integer.MIN_VALUE;

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

                        int latHp = (int)Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
                        int lonHp = (int)Math.round(res[0] * (1<<Coord.DELTA_SHIFT));

                        switch (type) {
                        case PathIterator.SEG_MOVETO:
                                if (points != null)
                                        log.error("area not singular");
                                points = new ArrayList<>();
                                points.add(Coord.makeHighPrecCoord(latHp, lonHp, coordPool));
                                break;
                        case PathIterator.SEG_LINETO:
                                assert points != null;
                                if (prevLatHp != latHp || prevLongHp != lonHp)
                                        points.add(Coord.makeHighPrecCoord(latHp, lonHp, coordPool));
                                break;
                        case PathIterator.SEG_CLOSE:
                                assert points != null;
                                if (points.size() < 3)
                                        points = null;
                                else {
                                        if (points.get(0).highPrecEquals(points.get(points.size() - 1))) {
                                                // replace equal last with closing point
                                                points.set(points.size() - 1, points.get(0));
                                        }
                                        else
                                                points.add(points.get(0)); // add closing point
                                        if (points.size() < 4)
                                                points = null;
                                        else
                                                return points;
                                }
                                break;
                        default:
                                log.error("Unsupported path iterator type " + type
                                                + ". This is an mkgmap error.");
                        }

                        prevLatHp = latHp;
                        prevLongHp = lonHp;

                        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 discontinuous
         * 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) {
                return areaToShapes(area, null);
        }
       
        /**
         * Convert the area back into a list of polygons each represented by a list
         * of coords. It is possible that the area contains multiple discontinuous
         * 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.
         * @param coordPool map to produce unique Coord instances
         * @return a list of closed polygons
         */

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

                double[] res = new double[6];
                PathIterator pit = area.getPathIterator(null);
               
                List<Coord> coords = null;

                int prevLatHp = Integer.MIN_VALUE;
                int prevLongHp = Integer.MIN_VALUE;

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

                        int latHp = (int) Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
                        int lonHp = (int) Math.round(res[0] * (1<<Coord.DELTA_SHIFT));
                       
                        switch (type) {
                        case PathIterator.SEG_LINETO:
                                if (prevLatHp != latHp || prevLongHp != lonHp)
                                        coords.add(Coord.makeHighPrecCoord(latHp, lonHp, coordPool));
                               
                                prevLatHp = latHp;
                                prevLongHp = lonHp;
                                break;
                        case PathIterator.SEG_MOVETO:
                        case PathIterator.SEG_CLOSE:
                                if ((type == PathIterator.SEG_MOVETO && coords != null) || type == PathIterator.SEG_CLOSE) {
                                        if (coords.size() > 2){
                                                if (coords.get(0).highPrecEquals(coords.get(coords.size() - 1))){
                                                        // replace equal last with closing point
                                                        coords.set(coords.size() - 1, coords.get(0));
                                                }
                                                else
                                                        coords.add(coords.get(0)); // add closing point
                                        }
                                        if (coords.size() > 3){
                                                outputs.add(coords);
                                        }
                                }
                                if (type == PathIterator.SEG_MOVETO){
                                        coords = new ArrayList<>();
                                        coords.add(Coord.makeHighPrecCoord(latHp, lonHp, coordPool));
                                        prevLatHp = latHp;
                                        prevLongHp = lonHp;
                                } else {
                                        coords = null;
                                        prevLatHp = Integer.MIN_VALUE;
                                        prevLongHp = Integer.MIN_VALUE;
                                }
                                break;
                        default:
                                log.error("Unsupported path iterator type " + type
                                                + ". This is an mkgmap error.");
                        }

                        pit.next();
                }

                return outputs;
        }

        /**
         * Convert area with coordinates in degrees to area in MapUnits
         * @param area
         * @return
         */

        public static java.awt.geom.Area AreaDegreesToMapUnit(java.awt.geom.Area area){
                if (area == null)
                        return null;
                double[] res = new double[6];
                Path2D path = new Path2D.Double();
                PathIterator pit = area.getPathIterator(null);
                while (!pit.isDone()) {
                        int type = pit.currentSegment(res);

                        double fLat = res[1];
                        double fLon = res[0];
                        int lat = Utils.toMapUnit(fLat);
                        int lon = Utils.toMapUnit(fLon);
                       
                        switch (type) {
                        case PathIterator.SEG_LINETO:
                                path.lineTo(lon, lat);
                                break;
                        case PathIterator.SEG_MOVETO:
                                path.moveTo(lon, lat);
                                break;
                        case PathIterator.SEG_CLOSE:
                                path.closePath();
                                break;
                        default:
                                System.out.println("Unsupported path iterator type " + type
                                                + ". This is an internal splitter error.");
                        }

                        pit.next();
                }
                return new java.awt.geom.Area(path);
        }

        /**
         * Read am osmosis *.poly file that describes a polygon with coordinates in degrees.  
         * @param polygonFile path to the poly file
         * @return the polygon converted to map units or null in case of error.
         */

        public static java.awt.geom.Area readPolyFile(String polygonFile) {
                File f = new File(polygonFile);
                if (!f.exists()) {
                        throw new IllegalArgumentException("polygon file doesn't exist: " + polygonFile);
                }
                try {
                        PolygonFileReader pfr = new PolygonFileReader(f);
                        java.awt.geom.Area polygonInDegrees = pfr.loadPolygon();
                        return Java2DConverter.AreaDegreesToMapUnit(polygonInDegrees);
                } catch (Exception e) {
                        log.error("cannot read polygon file", polygonFile);
                        log.error(e);
                }
                return null;
        }


}