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