package org.opensha2.geo; import static com.google.common.base.Preconditions.checkNotNull; import static org.opensha2.geo.BorderType.MERCATOR_LINEAR; import java.awt.Shape; /** * Utility methods pertaining to geographic regions. * * <p>Note that intersection and union operations will discard any grid * information if {@code GriddedRegion}s are supplied as arguments. * * <p>To build complex gridded regions (e.g. circular or buffered, or via * intersections and unions) create a Region first and use * {@link Regions#toGridded(Region, double, double, Location)}. * * @author Peter Powers */ public class Regions { /** * Creates a {@code Region} from a list of border locations. The border type * specifies whether lat-lon values are treated as points in an orthogonal * coordinate system or as connecting great circles. The border * {@code LocationList} does not need to repeat the first {@code Location} at * the end of the list. * * @param name of the {@code Region}; may be {@code null} * @param border {@code Locations} * @param type the {@link BorderType} to use when initializing; a {@code null} * value defaults to {@code BorderType.MERCATOR_LINEAR} * @return a new {@code Region} * @throws IllegalArgumentException if the {@code border} does not have at * least 3 points, defines a {@code Region} that is empty, or consists * of more than a single closed path. * @throws NullPointerException if the {@code border} is {@code null} */ public static Region create(String name, LocationList border, BorderType type) { Region r = new Region(name); r.initBordered(border, type); return r; } /** * <img style="padding: 30px 40px; float: right;" src= * "{@docRoot}/resources/gridded_regions_border.jpg"/>Creates a * {@code GriddedRegion} from a list of border locations. * * <p>The border type specifies whether lat-lon values are treated as points * in an orthogonal coordinate system or as connecting great circles. The * border {@code LocationList} does not need to repeat the first * {@code Location} at the end of the list. If the supplied * {@code anchor Location} is {@code null}, it is automatically set to the * Location defined by the minimum latitude and longitude of the region's * border. * * @param name of the {@code GriddedRegion}; may be {@code null} * @param border {@code Locations} * @param type the {@link BorderType} to use when initializing; a {@code null} * value defaults to {@code BorderType.MERCATOR_LINEAR} * @param latSpacing of grid nodes * @param lonSpacing of grid nodes * @param anchor {@code Location} for grid; may be {@code null} * @return a new {@code GriddedRegion} * @throws IllegalArgumentException if the {@code border} does not have at * least 3 points, defines a {@code Region} that is empty, or consists * of more than a single closed path; or {@code spacing} is outside * the range 0° < {@code spacing} ≤ 5° * @throws NullPointerException if the {@code border} is {@code null} */ public static GriddedRegion createGridded(String name, LocationList border, BorderType type, double latSpacing, double lonSpacing, Location anchor) { GriddedRegion gr = new GriddedRegion(name); gr.initBordered(border, type); gr.initGrid(latSpacing, lonSpacing, anchor); return gr; } /** * Creates a {@code Region} from a pair of {@code Location }s. When viewed in * a Mercator projection, the {@code Region} will be a rectangle. If either * both latitude or both longitude values in the {@code Location}s are the * same, an exception is thrown. * * <p><b>Note:</b> Internally, the size of the region is expanded by a very * small value (~1m) to ensure that calls to {@link Region#contains(Location)} * for any {@code Location} on the north or east border of the region will * return {@code true} and that any double precision rounding issues do not * clip the south and west borders (e.g. 45.0 may be interpreted as * 44.9999...). See also the rules governing insidedness in the {@link Shape} * interface. * * @param name of the {@code Region}; may be {@code null} * @param loc1 the first {@code Location} * @param loc2 the second {@code Location} * @return a new rectangular (broadly-defined) {@code Region} * @throws IllegalArgumentException if the latitude or longitude values in the * {@code Location}s provided are the same * @throws NullPointerException if either {@code Location} argument is * {@code null} */ public static Region createRectangular(String name, Location loc1, Location loc2) { Region r = new Region(name); r.initRectangular(loc1, loc2); return r; } /** * Creates a {@code GriddedRegion} from a pair of {@code Location }s. When * viewed in a Mercator projection, the {@code Region} will be a rectangle. If * either both latitude or both longitude values in the {@code Location}s are * the same, an exception is thrown. If the supplied {@code anchor Location} * is {@code null}, it is automatically set to the Location defined by the * minimum latitude and longitude of the region's border. * * <p><b>Note:</b> Internally, the size of the region is expanded by a very * small value (~1m) to ensure that calls to {@link Region#contains(Location)} * for any {@code Location} on the north or east border of the region will * return {@code true} and that any double precision rounding issues do not * clip the south and west borders (e.g. 45.0 may be interpreted as * 44.9999...). See also the rules governing insidedness in the {@link Shape} * interface. * * @param name of the {@code GriddedRegion}; may be {@code null} * @param loc1 the first {@code Location} * @param loc2 the second {@code Location} * @param latSpacing of grid nodes * @param lonSpacing of grid nodes * @param anchor {@code Location} for grid; may be {@code null} * @return a new rectangular (broadly-defined) {@code GriddedRegion} * @throws IllegalArgumentException if the latitude or longitude values in the * {@code Location}s provided are the same or {@code spacing} is * outside the range 0° < {@code spacing} ≤ 5° * @throws NullPointerException if either {@code Location} argument is * {@code null} */ public static GriddedRegion createRectangularGridded(String name, Location loc1, Location loc2, double latSpacing, double lonSpacing, Location anchor) { GriddedRegion gr = new GriddedRegion(name); gr.initRectangular(loc1, loc2); gr.initGrid(latSpacing, lonSpacing, anchor); return gr; } /** * <img style="padding: 30px 40px; float: right;" src= * "{@docRoot}/resources/gridded_regions_circle.jpg"/>Creates a circular * {@code Region}. Internally, the centerpoint and radius are used to create a * circular region composed of straight line segments that span 10° wedges. * * <p>Passing the resultant region * {@link #toGridded(Region, double, double, Location)} yields a * {@link GriddedRegion} like that in the adjacent figure. The heavy black * line marks the border of the {@code Region}. The light gray dots mark the * {@code Location}s of nodes outside the region, and black dots those inside * the region. The dashed grey line marks the border, inside which, a * {@code Location} will be associated with a grid node. See * {@link GriddedRegion#indexForLocation(Location)} for more details on rules * governing whether a grid node is inside a region and whether a * {@code Location} will be associated with a grid node. * * @param name of the {@code Region}; may be {@code null} * @param center of the circle * @param radius of the circle * @return a new circular {@code Region} * @throws IllegalArgumentException if {@code radius} is outside the range 0 * km < {@code radius} ≤ 1000 km * @throws NullPointerException if {@code center} is {@code null} */ public static Region createCircular(String name, Location center, double radius) { Region r = new Region(name); r.initCircular(center, radius); return r; } /** * <img style="padding: 30px 40px; float: right;" src= * "{@docRoot}/resources/gridded_regions_buffer.jpg"/>Creates a {@code Region} * as a buffered area around a line. * * <p>Passing the resultant region * {@link #toGridded(Region, double, double, Location)} yields a * {@link GriddedRegion} like that in the adjacent figure. The light gray dots * mark the {@code Location}s of nodes outside the region, and black dots * those inside the region. The dashed grey line marks the border, inside * which, a {@code Location} will be associated with a grid node. See * {@link GriddedRegion#indexForLocation(Location)} for more details on rules * governing whether a grid node is inside a region and whether a * {@code Location} will be associated with a grid node. * * @param name of the {@code Region}; may be {@code null} * @param line at center of buffered {@code Region} * @param buffer distance from line * @return a new buffered {@code Region} around a line * @throws NullPointerException if {@code line} is {@code null} * @throws IllegalArgumentException if {@code buffer} is outside the range 0 * km < {@code buffer} ≤ 500 km */ public static Region createBuffered(String name, LocationList line, double buffer) { Region r = new Region(name); r.initBuffered(line, buffer); return r; } /** * Creates an exact copy of a {@code Region}. * * @return a copy of the supplied {@code Region} * @param region to copy * @throws NullPointerException if the supplied {@code Region} is {@code null} */ public static Region copyOf(Region region) { checkNotNull(region, "Supplied Region is null"); Region r = new Region(region.name()); r.initCopy(region); return r; } /** * Creates a copy of a {@code GriddedRegion}. * * @return a copy of the supplied {@code GriddedRegion} * @param region to copy * @throws NullPointerException if the supplied {@code GriddedRegion} is * {@code null} */ public static GriddedRegion copyOf(GriddedRegion region) { checkNotNull(region, "Supplied Region is null"); GriddedRegion gr = new GriddedRegion(region.name()); gr.initCopy(region); return gr; } /** * Creates a {@code GriddedRegion} with the same shape as the supplied * {@code Region}. If the supplied {@code anchor Location} is {@code null}, it * is automatically set to the Location defined by the minimum latitude and * longitude of the region's border. * * @param region to use as basis for new {@code GriddedRegion} * @param latSpacing of grid nodes * @param lonSpacing of grid nodes * @param anchor {@code Location} for grid; may be {@code null} * @return a new {@code GriddedRegion} * @throws IllegalArgumentException if {@code spacing} is outside the range 0° * < {@code spacing} ≤ 5° * @throws NullPointerException if the supplied {@code Region} is {@code null} */ public static GriddedRegion toGridded(Region region, double latSpacing, double lonSpacing, Location anchor) { checkNotNull(region, "Supplied Region is null"); GriddedRegion gr = new GriddedRegion(region.name()); // TODO if supplied region is in fact GR, check that still going to // Region.initCopy() gr.initCopy(region); gr.initGrid(latSpacing, lonSpacing, anchor); return gr; } /** * Returns the intersection of two {@code Region}s. If the {@code Region}s do * not overlap, the method returns {@code null}. * * @param name of the {@code Region}; may be {@code null} * @param r1 the first {@code Region} * @param r2 the second {@code Region} * @return a new {@code Region} defined by the intersection of {@code r1} and * {@code r2} or {@code null} if they do not overlap * @throws IllegalArgumentException if either supplied {@code Region} is not a * single closed {@code Region} * @throws NullPointerException if either supplied {@code Region} is * {@code null} */ public static Region intersectionOf(String name, Region r1, Region r2) { return Region.intersect(name, r1, r2); } /** * Returns the union of two {@code Region}s. If the {@code Region}s do not * overlap, the method returns {@code null}. * * @param name of the {@code Region}; may be {@code null} * @param r1 the first {@code Region} * @param r2 the second {@code Region} * @return a new {@code Region} defined by the union of {@code r1} and * {@code r2} or {@code null} if they do not overlap * @throws IllegalArgumentException if either supplied {@code Region} is not a * single closed {@code Region} * @throws NullPointerException if either supplied {@code Region} is * {@code null} */ public static Region unionOf(String name, Region r1, Region r2) { return Region.union(name, r1, r2); } /** * Convenience method to return a {@code Region} spanning the entire globe. * @return a {@code Region} extending from -180° to +180° longitude and -90° * to +90° latitude */ public static Region global() { LocationList locs = LocationList.create( Location.create(-90, -180), Location.create(-90, 180), Location.create(90, 180), Location.create(90, -180)); return create("Global Region", locs, MERCATOR_LINEAR); } }