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