/* * 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 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); 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 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 toString() { return "(" + Utils.toDegrees(minLat) + ',' + Utils.toDegrees(minLong) + ") to (" + Utils.toDegrees(maxLat) + ',' + Utils.toDegrees(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(x, y, a); 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 lat30 = co.getHighPrecLat(); int lon30 = co.getHighPrecLon(); return lat30 >= (minLat << Coord.DELTA_SHIFT) && lat30 <= (maxLat << Coord.DELTA_SHIFT) && lon30 >= (minLong << Coord.DELTA_SHIFT) && lon30 <= (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 lat30 = co.getHighPrecLat(); int lon30 = co.getHighPrecLon(); return lat30 > (minLat << Coord.DELTA_SHIFT) && lat30 < (maxLat << Coord.DELTA_SHIFT) && lon30 > (minLong << Coord.DELTA_SHIFT) && lon30 < (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; if (maxLat != area.maxLat) return false; if (maxLong != area.maxLong) return false; if (minLat != area.minLat) return false; if (minLong != area.minLong) return false; return true; } 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<Coord>(5); Coord start = new Coord(minLat, minLong); coords.add(start); Coord co = new Coord(minLat, maxLong); coords.add(co); co = new Coord(maxLat, maxLong); coords.add(co); co = new Coord(maxLat, minLong); coords.add(co); coords.add(start); return coords; } }