/*
 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences.
 * Copyright (C) 2006 - JScience (http://jscience.org/)
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package org.jscience.geography.coordinates;

import javax.measure.Measure;
import javax.measure.converter.UnitConverter;
import javax.measure.quantity.*;
import static javax.measure.unit.SI.*;
import javax.measure.unit.Unit;

import javolution.context.ObjectFactory;
import javolution.xml.XMLFormat;
import javolution.xml.stream.XMLStreamException;

import static org.jscience.geography.coordinates.crs.ReferenceEllipsoid.WGS84;

import org.jscience.geography.coordinates.crs.GeocentricCRS;
import org.jscience.mathematics.vector.DimensionException;
import org.jscience.mathematics.vector.Float64Vector;
import org.opengis.referencing.cs.CoordinateSystem;

/**
 * This class represents the {@link GeocentricCRS geocentric} Earth-Centered, 
 * Earth-Fixed (ECEF) cartesian coordinates used in GPS/GLONASS.
 *
 * @author Paul D. Anderson
 * @version 3.0, February 18, 2006
 */
public final class XYZ extends Coordinates> {

    /**
     * Holds the coordinate reference system for all instances of this class.
     */
    public static final GeocentricCRS CRS = new GeocentricCRS() {

        @Override
        protected XYZ coordinatesOf(AbsolutePosition position) {
            double latitude = position.latitudeWGS84.doubleValue(RADIAN);
            double longitude = position.longitudeWGS84.doubleValue(RADIAN);
            double height = (position.heightWGS84 != null) ?
                position.heightWGS84.doubleValue(METRE) : 0.0;

            double cosLat = Math.cos(latitude);
            double sinLat = Math.sin(latitude);
            double cosLon = Math.cos(longitude);
            double sinLon = Math.sin(longitude);

            double roc = WGS84.verticalRadiusOfCurvature(latitude);
            double x = (roc + height) * cosLat * cosLon;
            double y = (roc + height) * cosLat * sinLon;
            double z = ((1.0 - WGS84.getEccentricitySquared()) * roc + height)
                    * sinLat;

            return XYZ.valueOf(x, y, z, METRE);
        }

        @Override
        protected AbsolutePosition positionOf(XYZ coordinates,
                AbsolutePosition position) {
            final double x = coordinates._x;
            final double y = coordinates._y;
            final double z = coordinates._z;

            final double longitude = Math.atan2(y, x);

            final double latitude;
            final double xy = Math.hypot(x, y);
            // conventional result if xy == 0.0...
            if (xy == 0.0) {
                latitude = (z >= 0.0) ? Math.PI / 2.0 : -Math.PI / 2.0;
            } else {
                final double a = WGS84.getSemimajorAxis().doubleValue(METRE);
                final double b = WGS84.getsSemiminorAxis().doubleValue(METRE);
                final double ea2 = WGS84.getEccentricitySquared();
                final double eb2 = WGS84.getSecondEccentricitySquared();
                final double beta = Math.atan2(a * z, b * xy);
                double numerator = z + b * eb2 * cube(Math.sin(beta));
                double denominator = xy - a * ea2 * cube(Math.cos(beta));
                latitude = Math.atan2(numerator, denominator);
            }

            final double height = xy / Math.cos(latitude)
                    - WGS84.verticalRadiusOfCurvature(latitude);
            position.latitudeWGS84 = Measure.valueOf(latitude, RADIAN);
            position.longitudeWGS84 = Measure.valueOf(longitude, RADIAN);
            position.heightWGS84 = Measure.valueOf(height, METRE);
            return position;
        }

        @Override
        public CoordinateSystem getCoordinateSystem() {
            return GeocentricCRS.XYZ_CS;
        }
    };

    private static double cube(final double x) {
        return x * x * x;
    }

    /**
     * Holds the x position in meters.
     */
    private double _x;

    /**
     * Holds the y position in meters.
     */
    private double _y;

    /**
     * Holds the z position in meters.
     */
    private double _z;

    /**
     * Returns the spatial position corresponding to the specified coordinates.
     *
     * @param x the x value stated in the specified unit.
     * @param y the y value stated in the specified unit.
     * @param z the z value stated in the specified unit.
     * @param unit the length unit in which the coordinates are stated.
     * @return the corresponding 3D position.
     */
    public static XYZ valueOf(double x, double y, double z, Unit unit) {
        XYZ xyz = FACTORY.object();
        if (unit == METRE) {
            xyz._x = x;
            xyz._y = y;
            xyz._z = z;
        } else {
            UnitConverter toMeter = unit.getConverterTo(METRE);
            xyz._x = toMeter.convert(x);
            xyz._y = toMeter.convert(y);
            xyz._z = toMeter.convert(z);
        }
        return xyz;
    }

    private static final ObjectFactory FACTORY = new ObjectFactory() {

        @Override
        protected XYZ create() {
            return new XYZ();
        }
    };

    private XYZ() {
    }

    /**
     * Returns the spatial position corresponding to the specified 
     * 3-dimensional vector.
     *
     * @param vector the 3-dimensional vector holding the x/y/z coordinates.
     * @param unit the length unit in which the coordinates are stated.
     * @return the corresponding 3D position.
     */
    public static XYZ valueOf(Float64Vector vector, Unit unit) {
        if (vector.getDimension() != 3)
            throw new DimensionException("3-dimensional vector expected");
        return XYZ.valueOf(vector.getValue(0), vector.getValue(1), vector
                .getValue(2), unit);
    }

    /**
     * Returns the x coordinate value as double
     *
     * @param unit the length unit of the x coordinate value to return.
     * @return the x coordinate stated in the specified unit.
     */
    public double xValue(Unit unit) {
        return (unit == METRE) ? _x : METRE.getConverterTo(unit).convert(_x);
    }

    /**
     * Returns the y coordinate value as double
     *
     * @param unit the length unit of the x coordinate value to return.
     * @return the z coordinate stated in the specified unit.
     */
    public double yValue(Unit unit) {
        return (unit == METRE) ? _y : METRE.getConverterTo(unit).convert(_y);
    }

    /**
     * Returns the z coordinate value as double
     *
     * @param unit the length unit of the x coordinate value to return.
     * @return the z coordinate stated in the specified unit.
     */
    public double zValue(Unit unit) {
        return (unit == METRE) ? _z : METRE.getConverterTo(unit).convert(_z);
    }

    /**
     * Returns the x/y/z coordinates value as a 3-dimensional vector.
     *
     * @param unit the length unit of the vector coordinates.
     * @return a vector holding the x/y/z coordinates stated in the 
     *         specified unit.
     */
    public Float64Vector toVector(Unit unit) {
        if (unit == METRE)
            return Float64Vector.valueOf(_x, _y, _z);
        UnitConverter cvtr = METRE.getConverterTo(unit);
        return Float64Vector.valueOf(cvtr.convert(_x), cvtr.convert(_y), cvtr
                .convert(_z));
    }

    @Override
    public GeocentricCRS getCoordinateReferenceSystem() {
        return CRS;
    }

    // OpenGIS Interface.
    public int getDimension() {
        return 3;
    }

    // OpenGIS Interface.
    public double getOrdinate(int dimension) throws IndexOutOfBoundsException {
        if (dimension == 0) {
            Unit u = GeocentricCRS.XYZ_CS.getAxis(0).getUnit();
            return METRE.getConverterTo(u).convert(_x);
        } else if (dimension == 1) {
            Unit u = GeocentricCRS.XYZ_CS.getAxis(1).getUnit();
            return METRE.getConverterTo(u).convert(_y);
        } else if (dimension == 2) {
            Unit u = GeocentricCRS.XYZ_CS.getAxis(2).getUnit();
            return METRE.getConverterTo(u).convert(_z);
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public XYZ copy() {
        return XYZ.valueOf(_x, _y, _z, METRE);
    }

    // Default serialization.
    //

    static final XMLFormat XML = new XMLFormat(XYZ.class) {

        @Override
        public XYZ newInstance(Class cls, InputElement xml)
                throws XMLStreamException {
            return FACTORY.object();
        }

        public void write(XYZ xyz, OutputElement xml) throws XMLStreamException {
            xml.setAttribute("x", xyz._x);
            xml.setAttribute("y", xyz._y);
            xml.setAttribute("z", xyz._z);
        }

        public void read(InputElement xml, XYZ xyz) throws XMLStreamException {
            xyz._x = xml.getAttribute("x", 0.0);
            xyz._y = xml.getAttribute("y", 0.0);
            xyz._z = xml.getAttribute("z", 0.0);
        }
    };

    private static final long serialVersionUID = 1L;

}
 

Ads help maintain this website.