/*
* Copyright (C) 2006 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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.
*
*
* Author: Steve Ratcliffe
* Create date: 07-Dec-2006
*/
package uk.me.parabola.imgfmt.app;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.log.Logger;
/**
* A map area in map units. There is a constructor available for creating
* in lat/long form.
*
* @author Steve Ratcliffe
*/
public class Area {
private static final Logger log =
Logger.
getLogger(Area.
class);
public static final Area PLANET =
new Area(-
90.0, -
180.0,
90.0,
180.0);
private final int minLat
;
private final int minLong
;
private final int maxLat
;
private final int maxLong
;
/**
* Create an area from the given coordinates. We ensure that no dimension
* is zero.
*
* @param minLat The western latitude.
* @param minLong The southern longitude.
* @param maxLat The eastern lat.
* @param maxLong The northern long.
*/
public Area(int minLat,
int minLong,
int maxLat,
int maxLong
) {
this.
minLat = minLat
;
if (maxLat == minLat
)
this.
maxLat = minLat+
1;
else
this.
maxLat = maxLat
;
this.
minLong = minLong
;
if (minLong == maxLong
)
this.
maxLong = maxLong+
1;
else
this.
maxLong = maxLong
;
}
public Area(double minLat,
double minLong,
double maxLat,
double maxLong
)
{
this(Utils.
toMapUnit(minLat
), Utils.
toMapUnit(minLong
)
, Utils.
toMapUnit(maxLat
), Utils.
toMapUnit(maxLong
));
}
public static Area getBBox
(List<Coord
> points
) {
int tmpMinLat =
Integer.
MAX_VALUE;
int tmpMaxLat =
Integer.
MIN_VALUE;
int tmpMinLong =
Integer.
MAX_VALUE;
int tmpMaxLong =
Integer.
MIN_VALUE;
for (Coord co : points
) {
int lat = co.
getLatitude();
if (lat
< tmpMinLat
)
tmpMinLat = lat
;
if (lat
> tmpMaxLat
)
tmpMaxLat = lat
;
int lon = co.
getLongitude();
if (lon
< tmpMinLong
)
tmpMinLong = lon
;
if (lon
> tmpMaxLong
)
tmpMaxLong = lon
;
}
return new Area(tmpMinLat, tmpMinLong, tmpMaxLat, tmpMaxLong
);
}
public int getMinLat
() {
return minLat
;
}
public int getMinLong
() {
return minLong
;
}
public int getMaxLat
() {
return maxLat
;
}
public int getMaxLong
() {
return maxLong
;
}
public int getWidth
() {
return maxLong - minLong
;
}
public int getHeight
() {
return maxLat - minLat
;
}
public Coord getCenter
() {
return new Coord
((minLat + maxLat
)/
2,
(minLong + maxLong
)/
2);// high prec not needed
}
public String debugString
() {
return String.
format(Locale.
ROOT,
"(%d, %d) to (%d, %d) (%.6f, %.6f) to (%.6f, %.6f)",
minLat, minLong,
maxLat, maxLong,
Utils.
toDegrees(minLat
), Utils.
toDegrees(minLong
),
Utils.
toDegrees(maxLat
), Utils.
toDegrees(maxLong
));
}
public String toString
() {
return "("
+ Utils.
toDegrees(minLat
) +
','
+ Utils.
toDegrees(minLong
) +
") to ("
+ Utils.
toDegrees(maxLat
) +
','
+ Utils.
toDegrees(maxLong
) +
')'
;
}
public String toHexString
() {
return "(0x"
+
Integer.
toHexString(minLat
) +
",0x"
+
Integer.
toHexString(minLong
) +
") to (0x"
+
Integer.
toHexString(maxLat
) +
",0x"
+
Integer.
toHexString(maxLong
) +
')';
}
/**
* Round integer to nearest power of 2.
*
* @param val The number of be rounded.
* @param shift The power of 2.
* @return The rounded number (binary half rounds up).
*/
private static int roundPof2
(int val,
int shift
) {
if (shift
<=
0)
return val
;
return (((val
>> (shift-
1)) +
1) >> 1) << shift
;
}
/**
* Split this area up into a number of smaller areas.
*
* @param xsplit The number of pieces to split this area into in the x
* direction.
* @param ysplit The number of pieces to split this area into in the y
* direction.
* @param resolutionShift round to this power of 2.
* @return An array containing xsplit*ysplit areas or null if can't split in half.
* @throws MapFailedException if more complex split operation couldn't be honoured.
*/
public Area[] split
(int xsplit,
int ysplit,
int resolutionShift
) {
Area[] areas =
new Area[xsplit
* ysplit
];
int xstart
;
int xend
;
int ystart
;
int yend
;
int nAreas =
0;
xstart = minLong
;
for (int x =
0; x
< xsplit
; x++
) {
if (x == xsplit -
1)
xend = maxLong
;
else
xend = roundPof2
(xstart +
(maxLong - xstart
) /
(xsplit - x
), resolutionShift
);
ystart = minLat
;
for (int y =
0; y
< ysplit
; y++
) {
if (y == ysplit -
1)
yend = maxLat
;
else
yend = roundPof2
(ystart +
(maxLat - ystart
) /
(ysplit - y
), resolutionShift
);
if (xstart
< xend
&& ystart
< yend
) {
Area a =
new Area(ystart, xstart, yend, xend
);
log.
debug("Area.split", minLat, minLong, maxLat, maxLong,
"res", resolutionShift,
"to", ystart,
xstart, yend, xend
);
areas
[nAreas++
] = a
;
} else {
log.
warn("Area.split", minLat, minLong, maxLat, maxLong,
"res", resolutionShift,
"can't", xsplit,
ysplit
);
}
ystart = yend
;
}
xstart = xend
;
}
if (nAreas == areas.
length) // no problem
return areas
;
// beware - MapSplitter.splitMaxSize requests split of 1/1 if the original area wasn't too big
else if (nAreas ==
1) // failed to split in half
return null;
else if (areas.
length ==
1 && areas
[0] ==
null)
return null;
else
throw new MapFailedException
("Area split shift align problems");
}
/**
* Get the largest dimension. So either the width or height, depending
* on which is larger.
*
* @return The largest dimension in map units.
*/
public int getMaxDimension
() {
return Math.
max(getWidth
(), getHeight
());
}
/**
*
* @param co a coord
* @return true if co is inside the Area (it may touch the boundary)
*/
public final boolean contains
(Coord co
) {
int latHp = co.
getHighPrecLat();
int lonHp = co.
getHighPrecLon();
return latHp
>=
(minLat
<< Coord.
DELTA_SHIFT)
&& latHp
<=
(maxLat
<< Coord.
DELTA_SHIFT)
&& lonHp
>=
(minLong
<< Coord.
DELTA_SHIFT)
&& lonHp
<=
(maxLong
<< Coord.
DELTA_SHIFT);
}
/**
*
* @param other an area
* @return true if the other area is inside the Area (it may touch the boundary)
*/
public final boolean contains
(Area other
) {
return other.
getMinLat() >= minLat
&& other.
getMaxLat() <= maxLat
&& other.
getMinLong() >= minLong
&& other.
getMaxLong() <= maxLong
;
}
/**
* @param co a coord
* @return true if co is inside the Area and doesn't touch the boundary
*/
public final boolean insideBoundary
(Coord co
) {
int latHp = co.
getHighPrecLat();
int lonHp = co.
getHighPrecLon();
return latHp
> (minLat
<< Coord.
DELTA_SHIFT)
&& latHp
< (maxLat
<< Coord.
DELTA_SHIFT)
&& lonHp
> (minLong
<< Coord.
DELTA_SHIFT)
&& lonHp
< (maxLong
<< Coord.
DELTA_SHIFT);
}
/**
*
* @param other an area
* @return true if the other area is inside the Area and doesn't touch the boundary
*/
public final boolean insideBoundary
(Area other
) {
return other.
getMinLat() > minLat
&& other.
getMaxLat() < maxLat
&& other.
getMinLong() > minLong
&& other.
getMaxLong() < maxLong
;
}
/**
* @param co
* @return true if co is on the boundary
*/
public final boolean onBoundary
(Coord co
) {
return contains
(co
) && !insideBoundary
(co
);
}
/**
* Checks if this area intersects the given bounding box at least
* in one point.
*
* @param bbox an area
* @return <code>true</code> if this area intersects the bbox;
* <code>false</code> else
*/
public final boolean intersects
(Area bbox
) {
return minLat
<= bbox.
getMaxLat() && maxLat
>= bbox.
getMinLat() &&
minLong
<= bbox.
getMaxLong() && maxLong
>= bbox.
getMinLong();
}
public boolean isEmpty
() {
return minLat
>= maxLat || minLong
>= maxLong
;
}
/**
*
* @param coords a list of coord instances
* @return false if any of the coords lies on or outside of this area
*/
public boolean allInsideBoundary
(List<Coord
> coords
) {
for (Coord co : coords
) {
if (!insideBoundary
(co
))
return false;
}
return true;
}
public boolean equals
(Object o
) {
if (this == o
)
return true;
if (o ==
null || getClass
() != o.
getClass())
return false;
Area area =
(Area) o
;
return maxLat == area.
maxLat && maxLong == area.
maxLong && minLat == area.
minLat && minLong == area.
minLong;
}
public int hashCode
() {
int result = minLat
;
result =
31 * result + minLong
;
result =
31 * result + maxLat
;
result =
31 * result + maxLong
;
return result
;
}
/**
* @return list of coords that form the rectangle
*/
public List<Coord
> toCoords
(){
List<Coord
> coords =
new ArrayList<>(5);
coords.
add(new Coord
(minLat, minLong
));
coords.
add(new Coord
(minLat, maxLong
));
coords.
add(new Coord
(maxLat, maxLong
));
coords.
add(new Coord
(maxLat, minLong
));
coords.
add(coords.
get(0));
return coords
;
}
public Area intersect
(Area other
) {
return new Area(Math.
max(minLat, other.
minLat),
Math.
max(minLong, other.
minLong),
Math.
min(maxLat, other.
maxLat),
Math.
min(maxLong, other.
maxLong));
}
}