package com.revolsys.geometry.cs;
import java.util.ArrayList;
import java.util.List;
import javax.measure.converter.UnitConverter;
import javax.measure.quantity.Angle;
import javax.measure.quantity.Length;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import com.revolsys.geometry.cs.projection.CoordinatesProjection;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.GeometryFactory;
public class GeographicCoordinateSystem implements CoordinateSystem {
public static final double EARTH_RADIUS = 6378137;
private static final long serialVersionUID = 8655274386401351222L;
public static double distanceMetres(final double lon1, final double lat1, final double lon2,
final double lat2) {
final double lon1Radians = Math.toRadians(lon1);
final double lon2Radians = Math.toRadians(lon2);
final double width = lon2Radians - lon1Radians;
final double lat1Radians = Math.toRadians(lat1);
final double lat2Radians = Math.toRadians(lat2);
final double height = lat2Radians - lat1Radians;
final double sinHeightOver2 = Math.sin(height / 2);
final double sinWidthOver2 = Math.sin(width / 2);
final double distance = 2 * EARTH_RADIUS * Math.asin(Math.sqrt(sinHeightOver2 * sinHeightOver2
+ Math.cos(lat1Radians) * Math.cos(lat2Radians) * sinWidthOver2 * sinWidthOver2));
return distance;
}
private final AngularUnit angularUnit;
private Area area;
private final Authority authority;
private final List<Axis> axis = new ArrayList<>();
private final Datum datum;
private boolean deprecated;
private final int id;
private final String name;
private final PrimeMeridian primeMeridian;
public GeographicCoordinateSystem(final int id, final String name, final Datum datum,
final AngularUnit angularUnit, final List<Axis> axis, final Area area,
final Authority authority, final boolean deprecated) {
this.id = id;
this.name = name;
this.datum = datum;
this.primeMeridian = null;
this.angularUnit = angularUnit;
if (axis != null && !axis.isEmpty()) {
this.axis.addAll(axis);
}
this.area = area;
this.authority = authority;
}
public GeographicCoordinateSystem(final int id, final String name, final Datum datum,
final PrimeMeridian primeMeridian, final AngularUnit angularUnit, final List<Axis> axis,
final Area area, final Authority authority, final boolean deprecated) {
this.id = id;
this.name = name;
this.datum = datum;
this.primeMeridian = primeMeridian;
this.angularUnit = angularUnit;
if (axis != null && !axis.isEmpty()) {
this.axis.addAll(axis);
}
this.area = area;
this.authority = authority;
this.deprecated = deprecated;
}
public GeographicCoordinateSystem(final int id, final String name, final Datum datum,
final PrimeMeridian primeMeridian, final AngularUnit angularUnit, final List<Axis> axis,
final Authority authority) {
this.id = id;
this.name = name;
this.datum = datum;
this.primeMeridian = primeMeridian;
this.angularUnit = angularUnit;
if (axis != null && !axis.isEmpty()) {
this.axis.addAll(axis);
}
this.authority = authority;
}
@Override
public GeographicCoordinateSystem clone() {
try {
return (GeographicCoordinateSystem)super.clone();
} catch (final Exception e) {
return null;
}
}
@Override
public boolean equals(final Object object) {
if (object == null) {
return false;
} else if (object == this) {
return true;
} else if (object instanceof GeographicCoordinateSystem) {
final GeographicCoordinateSystem cs = (GeographicCoordinateSystem)object;
if (!equals(this.datum, cs.datum)) {
return false;
} else if (!equals(getPrimeMeridian(), cs.getPrimeMeridian())) {
return false;
} else if (!equals(this.angularUnit, cs.angularUnit)) {
return false;
} else {
return true;
}
} else {
return false;
}
}
private boolean equals(final Object object1, final Object object2) {
if (object1 == object2) {
return true;
} else if (object1 == null || object2 == null) {
return false;
} else {
return object1.equals(object2);
}
}
public boolean equalsExact(final GeographicCoordinateSystem cs) {
if (cs == null) {
return false;
} else if (cs == this) {
return true;
} else {
if (!equals(this.angularUnit, cs.angularUnit)) {
return false;
} else if (!equals(this.area, cs.area)) {
return false;
} else if (!equals(this.authority, cs.authority)) {
return false;
} else if (!equals(this.axis, cs.axis)) {
return false;
} else if (!equals(this.datum, cs.datum)) {
return false;
} else if (this.deprecated != cs.deprecated) {
return false;
} else if (this.id != cs.id) {
return false;
} else if (!equals(this.name, cs.name)) {
return false;
} else if (!equals(getPrimeMeridian(), cs.getPrimeMeridian())) {
return false;
} else {
return true;
}
}
}
public AngularUnit getAngularUnit() {
return this.angularUnit;
}
@Override
public Area getArea() {
return this.area;
}
@Override
public BoundingBox getAreaBoundingBox() {
final GeometryFactory geometryFactory = getGeometryFactory();
if (this.area != null) {
return this.area.getLatLonBounds().convert(geometryFactory);
} else {
return geometryFactory.newBoundingBox(-180, -90, 180, 90);
}
}
@Override
public Authority getAuthority() {
return this.authority;
}
@Override
public List<Axis> getAxis() {
return this.axis;
}
@Override
public CoordinatesProjection getCoordinatesProjection() {
return null;
}
@Override
public int getCoordinateSystemId() {
return this.id;
}
@Override
public String getCoordinateSystemName() {
return this.name;
}
public Datum getDatum() {
return this.datum;
}
@Override
public Unit<Length> getLengthUnit() {
final Unit<Angle> unit = this.angularUnit.getUnit();
final UnitConverter radianConverter = unit.getConverterTo(SI.RADIAN);
final Spheroid spheroid = this.datum.getSpheroid();
final double radius = spheroid.getSemiMajorAxis();
final double radianFactor = radianConverter.convert(1);
return SI.METRE.times(radius).times(radianFactor);
}
public PrimeMeridian getPrimeMeridian() {
if (this.primeMeridian == null) {
if (this.datum == null) {
return null;
} else {
return this.datum.getPrimeMeridian();
}
} else {
return this.primeMeridian;
}
}
@Override
@SuppressWarnings("unchecked")
public Unit<Angle> getUnit() {
return this.angularUnit.getUnit();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
if (this.datum != null) {
result = prime * result + this.datum.hashCode();
}
if (getPrimeMeridian() != null) {
result = prime * result + getPrimeMeridian().hashCode();
}
return result;
}
@Override
public boolean isDeprecated() {
return this.deprecated;
}
@Override
public String toString() {
return this.name;
}
}