/*
* Copyright (C) 2017.
*
* 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.mkgmap.reader.osm;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Queue;
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;
import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation.JoinedWay;
import uk.me.parabola.util.Java2DConverter;
/**
* Methods to cut an MP-relation so that holes are connected with the outer way(s).
* Extracted from {@link MultiPolygonRelation}.
*
* @author WanMil
* @author Gerd Petermann
*
*/
public class MultiPolygonCutter
{
private static final Logger log =
Logger.
getLogger(MultiPolygonCutter.
class);
private final MultiPolygonRelation rel
;
private final Area tileArea
;
/**
* Create cutter for a given MP-relation and tile
* @param multiPolygonRelation the MP-relation
* @param tileArea the java area of the tile
*/
public MultiPolygonCutter
(MultiPolygonRelation multiPolygonRelation,
Area tileArea
) {
rel = multiPolygonRelation
;
this.
tileArea = tileArea
;
}
/**
* Cut out all inner polygons from the outer polygon. This will divide the outer
* polygon in several polygons.
* @param multiPolygonRelation
*
* @param outerPolygon
* the outer polygon
* @param innerPolygons
* a list of inner polygons
* @return a list of polygons that make the outer polygon cut by the inner
* polygons
*/
public List<Way
> cutOutInnerPolygons
(Way outerPolygon,
List<Way
> innerPolygons
) {
if (innerPolygons.
isEmpty()) {
Way outerWay =
new JoinedWay
(outerPolygon
);
if (log.
isDebugEnabled()) {
log.
debug("Way", outerPolygon.
getId(),
"splitted to way", outerWay.
getId());
}
return Collections.
singletonList(outerWay
);
}
// use the java.awt.geom.Area class because it's a quick
// implementation of what's needed
// this list contains all non overlapping and singular areas
// of the outerPolygon
Queue<AreaCutData
> areasToCut =
new LinkedList<>();
Collection<Area> finishedAreas =
new ArrayList<>(innerPolygons.
size());
// create a list of Area objects from the outerPolygon (clipped to the bounding box)
List<Area> outerAreas = createAreas
(outerPolygon,
true);
// create the inner areas
List<Area> innerAreas =
new ArrayList<>(innerPolygons.
size()+
2);
for (Way innerPolygon : innerPolygons
) {
// don't need to clip to the bounding box because
// these polygons are just used to cut out holes
innerAreas.
addAll(createAreas
(innerPolygon,
false));
}
// initialize the cut data queue
if (innerAreas.
isEmpty()) {
// this is a multipolygon without any inner areas
// nothing to cut
finishedAreas.
addAll(outerAreas
);
} else if (outerAreas.
size() ==
1) {
// there is one outer area only
// it is checked before that all inner areas are inside this outer area
AreaCutData initialCutData =
new AreaCutData
();
initialCutData.
outerArea = outerAreas.
get(0);
initialCutData.
innerAreas = innerAreas
;
areasToCut.
add(initialCutData
);
} else {
// multiple outer areas
for (Area outerArea : outerAreas
) {
AreaCutData initialCutData =
new AreaCutData
();
initialCutData.
outerArea = outerArea
;
initialCutData.
innerAreas =
new ArrayList<>(innerAreas
.
size());
for (Area innerArea : innerAreas
) {
if (outerArea.
getBounds2D().
intersects(
innerArea.
getBounds2D())) {
initialCutData.
innerAreas.
add(innerArea
);
}
}
if (initialCutData.
innerAreas.
isEmpty()) {
// this is either an error
// or the outer area has been cut into pieces on the tile bounds
finishedAreas.
add(outerArea
);
} else {
areasToCut.
add(initialCutData
);
}
}
}
while (!areasToCut.
isEmpty()) {
AreaCutData areaCutData = areasToCut.
poll();
CutPoint cutPoint = calcNextCutPoint
(areaCutData
);
if (cutPoint ==
null) {
finishedAreas.
add(areaCutData.
outerArea);
continue;
}
assert cutPoint.
getNumberOfAreas() > 0 :
"Number of cut areas == 0 in mp " + rel.
getId();
// cut out the holes
if (cutPoint.
getAreas().
size() ==
1)
areaCutData.
outerArea.
subtract(cutPoint.
getAreas().
get(0));
else {
// first combine the areas that should be subtracted
Path2D.
Double path =
new Path2D.
Double();
for (Area cutArea : cutPoint.
getAreas()) {
path.
append(cutArea,
false);
}
Area combinedCutAreas =
new Area(path
);
areaCutData.
outerArea.
subtract(combinedCutAreas
);
}
if (areaCutData.
outerArea.
isEmpty()) {
// this outer area space can be abandoned
continue;
}
// the inner areas of the cut point have been processed
// they are no longer needed
for (Area cutArea : cutPoint.
getAreas()) {
ListIterator<Area> areaIter = areaCutData.
innerAreas.
listIterator();
while (areaIter.
hasNext()) {
Area a = areaIter.
next();
if (a == cutArea
) {
areaIter.
remove();
break;
}
}
}
// remove all does not seem to work. It removes more than the identical areas.
// areaCutData.innerAreas.removeAll(cutPoint.getAreas());
if (areaCutData.
outerArea.
isSingular()) {
// the area is singular
// => no further splits necessary
if (areaCutData.
innerAreas.
isEmpty()) {
// this area is finished and needs no further cutting
finishedAreas.
add(areaCutData.
outerArea);
} else {
// read this area to further processing
areasToCut.
add(areaCutData
);
}
} else {
// we need to cut the area into two halves to get singular areas
Rectangle2D r1 = cutPoint.
getCutRectangleForArea(areaCutData.
outerArea,
true);
Rectangle2D r2 = cutPoint.
getCutRectangleForArea(areaCutData.
outerArea,
false);
// Now find the intersection of these two boxes with the
// original polygon. This will make two new areas, and each
// area will be one (or more) polygons.
Area a1 =
new Area(r1
);
Area a2 =
new Area(r2
);
a1.
intersect(areaCutData.
outerArea);
a2.
intersect(areaCutData.
outerArea);
if (areaCutData.
innerAreas.
isEmpty()) {
finishedAreas.
addAll(Java2DConverter.
areaToSingularAreas(a1
));
finishedAreas.
addAll(Java2DConverter.
areaToSingularAreas(a2
));
} else {
ArrayList<Area> cuttedAreas =
new ArrayList<>();
cuttedAreas.
addAll(Java2DConverter.
areaToSingularAreas(a1
));
cuttedAreas.
addAll(Java2DConverter.
areaToSingularAreas(a2
));
for (Area nextOuterArea : cuttedAreas
) {
ArrayList<Area> nextInnerAreas =
null;
// go through all remaining inner areas and check if they
// must be further processed with the nextOuterArea
for (Area nonProcessedInner : areaCutData.
innerAreas) {
if (nextOuterArea.
intersects(nonProcessedInner.
getBounds2D())) {
if (nextInnerAreas ==
null) {
nextInnerAreas =
new ArrayList<>();
}
nextInnerAreas.
add(nonProcessedInner
);
}
}
if (nextInnerAreas ==
null || nextInnerAreas.
isEmpty()) {
finishedAreas.
add(nextOuterArea
);
} else {
AreaCutData outCutData =
new AreaCutData
();
outCutData.
outerArea = nextOuterArea
;
outCutData.
innerAreas= nextInnerAreas
;
areasToCut.
add(outCutData
);
}
}
}
}
}
// convert the java.awt.geom.Area back to the mkgmap way
List<Way
> cuttedOuterPolygon =
new ArrayList<>(finishedAreas.
size());
Long2ObjectOpenHashMap
<Coord
> commonCoordMap =
new Long2ObjectOpenHashMap
<>();
for (Area area : finishedAreas
) {
Way w = singularAreaToWay
(area, rel.
getOriginalId());
if (w
!=
null) {
w.
setFakeId();
// make sure that equal coords are changed to identical coord instances
// this allows merging in the ShapeMerger
int n = w.
getPoints().
size();
for (int i =
0; i
< n
; i++
){
Coord p = w.
getPoints().
get(i
);
long key = Utils.
coord2Long(p
);
Coord replacement = commonCoordMap.
get(key
);
if (replacement ==
null)
commonCoordMap.
put(key, p
);
else {
assert p.
highPrecEquals(replacement
);
w.
getPoints().
set(i, replacement
);
}
}
w.
copyTags(outerPolygon
);
cuttedOuterPolygon.
add(w
);
if (log.
isDebugEnabled()) {
log.
debug("Way", outerPolygon.
getId(),
"splitted to way", w.
getId());
}
}
}
return cuttedOuterPolygon
;
}
private static CutPoint calcNextCutPoint
(AreaCutData areaData
) {
if (areaData.
innerAreas ==
null || areaData.
innerAreas.
isEmpty()) {
return null;
}
Rectangle2D outerBounds = areaData.
outerArea.
getBounds2D();
if (areaData.
innerAreas.
size() ==
1) {
// make it short if there is only one inner area
CutPoint cutPoint1 =
new CutPoint
(CoordinateAxis.
LATITUDE, outerBounds
);
cutPoint1.
addArea(areaData.
innerAreas.
get(0));
CutPoint cutPoint2 =
new CutPoint
(CoordinateAxis.
LONGITUDE, outerBounds
);
cutPoint2.
addArea(areaData.
innerAreas.
get(0));
if (cutPoint1.
compareTo(cutPoint2
) > 0) {
return cutPoint1
;
} else {
return cutPoint2
;
}
}
ArrayList<Area> innerStart =
new ArrayList<>(areaData.
innerAreas);
// first try to cut out all polygons that intersect the boundaries of the outer polygon
// this has the advantage that the outer polygon need not be split into two halves
for (CoordinateAxis axis : CoordinateAxis.
values()) {
CutPoint edgeCutPoint =
new CutPoint
(axis, outerBounds
);
// go through the inner polygon list and use all polygons that intersect the outer polygons bbox at the start
Collections.
sort(innerStart,
(axis == CoordinateAxis.
LONGITUDE ? COMP_LONG_START: COMP_LAT_START
));
for (Area anInnerStart : innerStart
) {
if (axis.
getStart30(anInnerStart
) <= axis.
getStart30(outerBounds
)) {
// found a touching area
edgeCutPoint.
addArea(anInnerStart
);
} else {
break;
}
}
if (edgeCutPoint.
getNumberOfAreas() > 0) {
// there at least one intersecting inner polygon
return edgeCutPoint
;
}
Collections.
sort(innerStart,
(axis == CoordinateAxis.
LONGITUDE ? COMP_LONG_STOP: COMP_LAT_STOP
));
// go through the inner polygon list and use all polygons that intersect the outer polygons bbox at the stop
for (Area anInnerStart : innerStart
) {
if (axis.
getStop30(anInnerStart
) >= axis.
getStop30(outerBounds
)) {
// found a touching area
edgeCutPoint.
addArea(anInnerStart
);
} else {
break;
}
}
if (edgeCutPoint.
getNumberOfAreas() > 0) {
// there at least one intersecting inner polygon
return edgeCutPoint
;
}
}
ArrayList<CutPoint
> bestCutPoints =
new ArrayList<>(CoordinateAxis.
values().
length);
for (CoordinateAxis axis : CoordinateAxis.
values()) {
CutPoint bestCutPoint =
new CutPoint
(axis, outerBounds
);
CutPoint currentCutPoint =
new CutPoint
(axis, outerBounds
);
Collections.
sort(innerStart,
(axis == CoordinateAxis.
LONGITUDE ? COMP_LONG_START: COMP_LAT_START
));
for (Area anInnerStart : innerStart
) {
currentCutPoint.
addArea(anInnerStart
);
if (currentCutPoint.
compareTo(bestCutPoint
) > 0) {
bestCutPoint = currentCutPoint.
duplicate();
}
}
bestCutPoints.
add(bestCutPoint
);
}
return Collections.
max(bestCutPoints
);
}
/**
* Create the areas that are enclosed by the way. Usually the result should
* only be one area but some ways contain intersecting lines. To handle these
* erroneous cases properly the method might return a list of areas.
*
* @param w a closed way
* @param clipBbox true if the areas should be clipped to the bounding box; false else
* @return a list of enclosed ares
*/
private List<Area> createAreas
(Way w,
boolean clipBbox
) {
Area area = Java2DConverter.
createArea(w.
getPoints());
if (clipBbox
&& !tileArea.
contains(area.
getBounds2D())) {
// the area intersects the bounding box => clip it
area.
intersect(tileArea
);
}
List<Area> areaList = Java2DConverter.
areaToSingularAreas(area
);
if (log.
isDebugEnabled()) {
log.
debug("Bbox clipped way",w.
getId()+
"=>",areaList.
size(),
"distinct area(s).");
}
return areaList
;
}
/**
* 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
* @param wayId
* the wayid for the new way
* @return a new mkgmap way
*/
private Way singularAreaToWay
(Area area,
long wayId
) {
List<Coord
> points = Java2DConverter.
singularAreaToPoints(area
);
if (points ==
null || points.
isEmpty()) {
if (log.
isDebugEnabled()) {
log.
debug("Empty area", wayId +
".", rel.
toBrowseURL());
}
return null;
}
return new Way
(wayId, points
);
}
private static class AreaCutData
{
Area outerArea
;
List<Area> innerAreas
;
}
private static final int CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD =
1<<(11 + Coord.
DELTA_SHIFT);
private static final int CUT_POINT_CLASSIFICATION_BAD_THRESHOLD =
1<< (8 + Coord.
DELTA_SHIFT);
private static class CutPoint
implements Comparable<CutPoint
>{
private int startPoint30 =
Integer.
MAX_VALUE; // 30 bits precision map units
private int stopPoint30 =
Integer.
MIN_VALUE; // 30 bits precision map units
private Integer cutPoint30 =
null; // 30 bits precision map units
private final LinkedList<Area> areas
;
private final Comparator<Area> comparator
;
private final CoordinateAxis axis
;
private Rectangle2D bounds
;
private final Rectangle2D outerBounds
;
private Double minAspectRatio
;
public CutPoint
(CoordinateAxis axis,
Rectangle2D outerBounds
) {
this.
axis = axis
;
this.
outerBounds = outerBounds
;
this.
areas =
new LinkedList<>();
this.
comparator =
(axis == CoordinateAxis.
LONGITUDE ? COMP_LONG_STOP : COMP_LAT_STOP
);
}
public CutPoint duplicate
() {
CutPoint newCutPoint =
new CutPoint
(this.
axis,
this.
outerBounds);
newCutPoint.
areas.
addAll(areas
);
newCutPoint.
startPoint30 = startPoint30
;
newCutPoint.
stopPoint30 = stopPoint30
;
return newCutPoint
;
}
private boolean isGoodCutPoint
() {
// It is better if the cutting line is on a multiple of 2048.
// Otherwise MapSource and QLandkarteGT paints gaps between the cuts
return getCutPoint30
() % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD ==
0;
}
private boolean isBadCutPoint
() {
int d1 = getCutPoint30
() - startPoint30
;
int d2 = stopPoint30 - getCutPoint30
();
return Math.
min(d1, d2
) < CUT_POINT_CLASSIFICATION_BAD_THRESHOLD
;
}
private boolean isStartCut
() {
return (startPoint30
<= axis.
getStart30(outerBounds
));
}
private boolean isStopCut
() {
return (stopPoint30
>= axis.
getStop30(outerBounds
));
}
/**
* Calculates the point where the cut should be applied.
* @return the point of cut
*/
private int getCutPoint30
() {
if (cutPoint30
!=
null) {
// already calculated => just return it
return cutPoint30
;
}
if (startPoint30 == stopPoint30
) {
// there is no choice => return the one possible point
cutPoint30 = startPoint30
;
return cutPoint30
;
}
if (isStartCut
()) {
// the polygons can be cut out at the start of the sector
// thats good because the big polygon need not to be cut into two halves
cutPoint30 = startPoint30
;
return cutPoint30
;
}
if (isStopCut
()) {
// the polygons can be cut out at the end of the sector
// thats good because the big polygon need not to be cut into two halves
cutPoint30 = startPoint30
;
return cutPoint30
;
}
// try to cut with a good aspect ratio so try the middle of the polygon to be cut
int midOuter30 = axis.
getStart30(outerBounds
)+
(axis.
getStop30(outerBounds
) - axis.
getStart30(outerBounds
)) /
2;
cutPoint30 = midOuter30
;
if (midOuter30
< startPoint30
) {
// not possible => the start point is greater than the middle so correct to the startPoint
cutPoint30 = startPoint30
;
if (((cutPoint30
& ~
(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-
1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD
) <= stopPoint30
) {
cutPoint30 =
((cutPoint30
& ~
(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-
1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD
);
}
} else if (midOuter30
> stopPoint30
) {
// not possible => the stop point is smaller than the middle so correct to the stopPoint
cutPoint30 = stopPoint30
;
if ((cutPoint30
& ~
(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-
1)) >= startPoint30
) {
cutPoint30 =
(cutPoint30
& ~
(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-
1));
}
}
// try to find a cut point that is a multiple of 2048 to
// avoid that gaps are painted by MapSource and QLandkarteGT
// between the cutting lines
int cutMod = cutPoint30
% CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD
;
if (cutMod ==
0) {
return cutPoint30
;
}
int cut1 =
(cutMod
> 0 ? cutPoint30-cutMod : cutPoint30 - CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD- cutMod
);
if (cut1
>= startPoint30
&& cut1
<= stopPoint30
) {
cutPoint30 = cut1
;
return cutPoint30
;
}
int cut2 =
(cutMod
> 0 ? cutPoint30 + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD -cutMod : cutPoint30 - cutMod
);
if (cut2
>= startPoint30
&& cut2
<= stopPoint30
) {
cutPoint30 = cut2
;
return cutPoint30
;
}
return cutPoint30
;
}
public Rectangle2D getCutRectangleForArea
(Area toCut,
boolean firstRect
) {
return getCutRectangleForArea
(toCut.
getBounds2D(), firstRect
);
}
public Rectangle2D getCutRectangleForArea
(Rectangle2D areaRect,
boolean firstRect
) {
double cp =
(double) getCutPoint30
() /
(1<<Coord.
DELTA_SHIFT);
if (axis == CoordinateAxis.
LONGITUDE) {
double newWidth = cp-areaRect.
getX();
if (firstRect
) {
return new Rectangle2D.Double(areaRect.
getX(), areaRect.
getY(), newWidth, areaRect.
getHeight());
} else {
return new Rectangle2D.Double(areaRect.
getX()+newWidth, areaRect.
getY(), areaRect.
getWidth()-newWidth, areaRect.
getHeight());
}
} else {
double newHeight = cp-areaRect.
getY();
if (firstRect
) {
return new Rectangle2D.Double(areaRect.
getX(), areaRect.
getY(), areaRect.
getWidth(), newHeight
);
} else {
return new Rectangle2D.Double(areaRect.
getX(), areaRect.
getY()+newHeight, areaRect.
getWidth(), areaRect.
getHeight()-newHeight
);
}
}
}
public List<Area> getAreas
() {
return areas
;
}
public void addArea
(Area area
) {
// remove all areas that do not overlap with the new area
while (!areas.
isEmpty() && axis.
getStop30(areas.
getFirst()) < axis.
getStart30(area
)) {
// remove the first area
areas.
removeFirst();
}
areas.
add(area
);
Collections.
sort(areas, comparator
);
startPoint30 = axis.
getStart30(Collections.
max(areas,
(axis == CoordinateAxis.
LONGITUDE ? COMP_LONG_START
: COMP_LAT_START
)));
stopPoint30 = axis.
getStop30(areas.
getFirst());
// reset the cached value => need to be recalculated the next time they are needed
bounds =
null;
cutPoint30 =
null;
minAspectRatio =
null;
}
public int getNumberOfAreas
() {
return this.
areas.
size();
}
/**
* Retrieves the minimum aspect ratio of the outer bounds after cutting.
*
* @return minimum aspect ratio of outer bound after cutting
*/
public double getMinAspectRatio
() {
if (minAspectRatio ==
null) {
// first get the left/upper cut
Rectangle2D r1 = getCutRectangleForArea
(outerBounds,
true);
double s1_1 = CoordinateAxis.
LATITUDE.
getSizeOfSide(r1
);
double s1_2 = CoordinateAxis.
LONGITUDE.
getSizeOfSide(r1
);
double ar1 =
Math.
min(s1_1, s1_2
) /
Math.
max(s1_1, s1_2
);
// second get the right/lower cut
Rectangle2D r2 = getCutRectangleForArea
(outerBounds,
false);
double s2_1 = CoordinateAxis.
LATITUDE.
getSizeOfSide(r2
);
double s2_2 = CoordinateAxis.
LONGITUDE.
getSizeOfSide(r2
);
double ar2 =
Math.
min(s2_1, s2_2
) /
Math.
max(s2_1, s2_2
);
// get the minimum
minAspectRatio =
Math.
min(ar1, ar2
);
}
return minAspectRatio
;
}
public int compareTo
(CutPoint o
) {
if (this == o
) {
return 0;
}
// prefer a cut at the boundaries
if (isStartCut
() && o.
isStartCut() ==
false) {
return 1;
}
else if (isStartCut
() ==
false && o.
isStartCut()) {
return -
1;
}
else if (isStopCut
() && o.
isStopCut() ==
false) {
return 1;
}
else if (isStopCut
() ==
false && o.
isStopCut()) {
return -
1;
}
// handle the special case that a cut has no area
if (getNumberOfAreas
() ==
0) {
if (o.
getNumberOfAreas() ==
0) {
return 0;
} else {
return -
1;
}
} else if (o.
getNumberOfAreas() ==
0) {
return 1;
}
if (isBadCutPoint
() != o.
isBadCutPoint()) {
if (isBadCutPoint
()) {
return -
1;
} else
return 1;
}
double dAR = getMinAspectRatio
() - o.
getMinAspectRatio();
if (dAR
!=
0) {
return (dAR
> 0 ? 1 : -
1);
}
if (isGoodCutPoint
() != o.
isGoodCutPoint()) {
if (isGoodCutPoint
())
return 1;
else
return -
1;
}
// prefer the larger area that is split
double ss1 = axis.
getSizeOfSide(getBounds2D
());
double ss2 = o.
axis.
getSizeOfSide(o.
getBounds2D());
if (ss1-ss2
!=
0)
return Double.
compare(ss1,ss2
);
int ndiff = getNumberOfAreas
()-o.
getNumberOfAreas();
return ndiff
;
}
private Rectangle2D getBounds2D
() {
if (bounds ==
null) {
// lazy init
bounds =
new Rectangle2D.Double();
for (Area a : areas
)
bounds.
add(a.
getBounds2D());
}
return bounds
;
}
public String toString
() {
return axis +
" "+getNumberOfAreas
()+
" "+startPoint30+
" "+stopPoint30+
" "+getCutPoint30
();
}
}
private static enum CoordinateAxis
{
LATITUDE
(false), LONGITUDE
(true);
private CoordinateAxis
(boolean useX
) {
this.
useX = useX
;
}
private final boolean useX
;
public int getStart30
(Area area
) {
return getStart30
(area.
getBounds2D());
}
public int getStart30
(Rectangle2D rect
) {
double val =
(useX
? rect.
getX() : rect.
getY());
return (int)Math.
round(val
* (1<<Coord.
DELTA_SHIFT));
}
public int getStop30
(Area area
) {
return getStop30
(area.
getBounds2D());
}
public int getStop30
(Rectangle2D rect
) {
double val =
(useX
? rect.
getMaxX() : rect.
getMaxY());
return (int)Math.
round(val
* (1<<Coord.
DELTA_SHIFT));
}
public double getSizeOfSide
(Rectangle2D rect
) {
if (useX
) {
int lat30 =
(int)Math.
round(rect.
getY() * (1<<Coord.
DELTA_SHIFT));
Coord c1 = Coord.
makeHighPrecCoord(lat30, getStart30
(rect
));
Coord c2 = Coord.
makeHighPrecCoord(lat30, getStop30
(rect
));
return c1.
distance(c2
);
} else {
int lon30 =
(int)Math.
round(rect.
getX() * (1<<Coord.
DELTA_SHIFT));
Coord c1 = Coord.
makeHighPrecCoord(getStart30
(rect
), lon30
);
Coord c2 = Coord.
makeHighPrecCoord(getStop30
(rect
), lon30
);
return c1.
distance(c2
);
}
}
}
private static final AreaComparator COMP_LONG_START =
new AreaComparator
(
true, CoordinateAxis.
LONGITUDE);
private static final AreaComparator COMP_LONG_STOP =
new AreaComparator
(
false, CoordinateAxis.
LONGITUDE);
private static final AreaComparator COMP_LAT_START =
new AreaComparator
(
true, CoordinateAxis.
LATITUDE);
private static final AreaComparator COMP_LAT_STOP =
new AreaComparator
(
false, CoordinateAxis.
LATITUDE);
private static class AreaComparator
implements Comparator<Area> {
private final CoordinateAxis axis
;
private final boolean startPoint
;
public AreaComparator
(boolean startPoint, CoordinateAxis axis
) {
this.
startPoint = startPoint
;
this.
axis = axis
;
}
public int compare
(Area o1,
Area o2
) {
if (o1 == o2
) {
return 0;
}
if (startPoint
) {
int cmp = axis.
getStart30(o1
) - axis.
getStart30(o2
);
if (cmp ==
0) {
return axis.
getStop30(o1
) - axis.
getStop30(o2
);
} else {
return cmp
;
}
} else {
int cmp = axis.
getStop30(o1
) - axis.
getStop30(o2
);
if (cmp ==
0) {
return axis.
getStart30(o1
) - axis.
getStart30(o2
);
} else {
return cmp
;
}
}
}
}
}