/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.rdf.internal.gis; import java.text.DecimalFormat; import java.text.ParseException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * An immutable coordinate expressed as double precision decimal degrees on the * surface of (the Earth's) sphere. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class CoordinateDD implements ICoordinate { /** * Decimal degrees north (or south iff negative). */ final public double northSouth; /** * Decimal degrees east (or west iff negative). */ final public double eastWest; /* * min and max for northSouth and eastWest. */ private static final double MAX_NORTH_SOUTH = +90d; private static final double MIN_NORTH_SOUTH = -90d; private static final double MAX_EAST_WEST = +180d; private static final double MIN_EAST_WEST = -180d; /** * Format for decimal degrees with (at least) five digits of precision. */ private static final DecimalFormat FMT_DECIMAL_DEGREES; static { FMT_DECIMAL_DEGREES = new DecimalFormat(); // DecimalFormatSymbols symbols = new DecimalFormatSymbols( // Locale.ENGLISH); // // // override so that the grouping character looks like a decimal // // point. // symbols.setGroupingSeparator('.'); // // // override so that the decimal point will not be confused with the // // grouping character during parsing. // symbols.setDecimalSeparator(':'); // // override the symbols on our format. // FMT_DECIMAL_DEGREES.setDecimalFormatSymbols(symbols); // // change the grouping size to 5 digits to get our apparent 5 digits // // after the decimal. // FMT_DECIMAL_DEGREES.setGroupingSize(5); // // make sure that grouping is turned on. // FMT_DECIMAL_DEGREES.setGroupingUsed(true); // explicit positive prefix. FMT_DECIMAL_DEGREES.setPositivePrefix("+"); // explicit negative prefix. FMT_DECIMAL_DEGREES.setNegativePrefix("-"); FMT_DECIMAL_DEGREES.setMinimumFractionDigits(5); } /** * Constructor for a coordinate using decimal degrees. * * @param northSouth * Decimal degrees north (or south iff negative). The range is * [-90:+90]. * * @param eastWest * Decimal degrees east (or west iff negative). The range is * (-180:+180]. Note that a value of 180W is normalized to 180E * by this constructor. * * @exception IllegalArgumentException * if <i>northSouth</i> is out of the range [-90:+90]. * @exception IllegalArgumentException * if <i>eastWest</i> is out of the range [-180:+180]. */ public CoordinateDD(double northSouth, double eastWest) { this(northSouth, eastWest, false); } /** * Constructor for a coordinate using decimal degrees. * * @param northSouth * Decimal degrees north (or south iff negative). The range is * [-90:+90]. * * @param eastWest * Decimal degrees east (or west iff negative). The range is * (-180:+180]. * * @param adjustToMinMax * If this parameter is set to true we may pass in values that are out of * range. Such values are adjusted to the respective MIN and MAX * values. If set to false, exceptions are thrown. * * @exception IllegalArgumentException * if <i>adjustToMinMax</i> is false AND <i>northSouth</i> is out of the range [-90:+90]. * @exception IllegalArgumentException * if <i>adjustToMinMax</i> is false AND if <i>eastWest</i> is out of the range [-180:+180]. */ public CoordinateDD(double northSouth, double eastWest, boolean adjustToMinMax) { if (northSouth < MIN_NORTH_SOUTH || northSouth > MAX_NORTH_SOUTH) { if (adjustToMinMax) { northSouth = northSouth < MIN_NORTH_SOUTH ? MIN_NORTH_SOUTH : MAX_NORTH_SOUTH; } else { throw new IllegalArgumentException("NorthSouth: " + FMT_DECIMAL_DEGREES.format(northSouth)); } } if (eastWest < MIN_EAST_WEST || eastWest > MAX_EAST_WEST) { if (adjustToMinMax) { eastWest = eastWest < MIN_EAST_WEST ? MIN_EAST_WEST : MAX_EAST_WEST; } else { throw new IllegalArgumentException("EastWest: " + FMT_DECIMAL_DEGREES.format(eastWest)); } } this.northSouth = northSouth; this.eastWest = eastWest; } /** * Returns the coordinate in decimal degrees as * * <pre> * +32.30642, -122.61458 * </code> */ public String toString() { return FMT_DECIMAL_DEGREES.format(northSouth) + "," + FMT_DECIMAL_DEGREES.format(eastWest); } /** * Matches a latitude expressed as decimal degrees. * <dl> * <dt>group(1)</dt> * <dd>decimal degrees</dd> * </dl> */ static final String regex_lat = // "([-+]?\\d{1,2}\\.\\d{3,})[�*]?" // decimal degrees longitude. ; /** * Matches a longitude expressed as decimal degrees. * <dl> * <dt>group(1)</dt> * <dd>decimal degrees</dd> * </dl> */ static final String regex_long = // "([-+]?\\d{1,3}\\.\\d{3,})[�*]?" // decimal degrees longitude. ; /** * Matches a coordinate expressed as decimal degrees. * <dl> * <dt>group({@link #group_degreesNorth})</dt> * <dd>decimal degrees north/south (latitude)</dd> * <dt>group({@link #group_degreesEast})</dt> * <dd>decimal degrees east/west (longitude)</dd> * </dl> * * @see #regex_lat * @see #regex_long */ static final Pattern pattern_dd = Pattern.compile("^(" + // "(" + regex_lat + "(\\s?[/,]?\\s?)" + regex_long + ")" + // ")$"// ); /* * The integer identifier of the group in pattern_dd in which the * corresponding named component of the coordinate will be found. */ static final int group_degreesNorth = 3; static final int group_degreesEast = 5; /** * Parses coordinates expressed in decimal degrees. For example * * <pre> * 36.07263, -79.79197 * * 36.07263/-79.79197 * * 36.07263 -79.79197 * * +36.0726355 -79.7919754 * </pre> * * @param text * The text. * * @return A coordinate */ public static CoordinateDD parse(String text) throws ParseException { Matcher m = pattern_dd.matcher(text); if (m.matches()) { double northSouth = Double.parseDouble(m.group(group_degreesNorth)); double eastWest = Double.parseDouble(m.group(group_degreesEast)); return new CoordinateDD(northSouth, eastWest, false); } throw new ParseException("Not decimal degrees: [" + text + "]", 0); } public boolean equals(ICoordinate o) { if (o instanceof CoordinateDD) { return equals((CoordinateDD) o); } return false; } /** * True iff the two coordinates are exactly the same. * * @param o * * @return */ public boolean equals(CoordinateDD o) { return northSouth == o.northSouth && eastWest == o.eastWest; } /** * Convert to degrees, minutes and (tenths of) seconds. * * @return The coordinate expressed as degrees, minutes and (tenths of) * seconds. */ public CoordinateDMS toDMS() { /* * The whole units of degrees will remain the same (i.e. in 121.135� * longitude, start with 121�). * * Multiply the decimal by 60 (i.e. .135 * 60 = 8.1). * * The whole number becomes the minutes (8'). * * Take the remaining decimal and multiply by 60. (i.e. .1 * 60 = 6). * * The resulting number becomes the seconds (6"). Seconds can remain as * a decimal. * * Take your three sets of numbers and put them together, using the * symbols for degrees (�), minutes (�), and seconds (") (i.e. 121�8'6" * longitude) * * @see http://geography.about.com/library/howto/htdegrees.htm * * @see http://id.mind.net/~zona/mmts/trigonometryRealms/degMinSec/degMinSec.htm */ // FIXME toDMS is not implemented throw new UnsupportedOperationException(); } public double distance(ICoordinate o, UNITS units) { return CoordinateUtility.distance(this, o.toDD(), units); } public CoordinateDDM toDDM() { // TODO Auto-generated method stub (toDDM) return null; } public CoordinateDD toDD() { return this; } }