Subversion Repositories hibernate-spatial

Rev

Rev 21 | Rev 49 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/**
 * $Id: SDOGeometryType.java 40 2007-09-20 15:33:24Z maesenka $
 *
 * This file is part of Spatial Hibernate, an extension to the 
 * hibernate ORM solution for geographic data. 
 *  
 * Copyright © 2007 K.U. Leuven LRD, Spatial Applications Division, Belgium
 *
 * This work was partially supported by the European Commission, 
 * under the 6th Framework Programme, contract IST-2-004688-STP.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more information, visit: http://www.cadrie.com/
 */
 
package org.hibernatespatial.oracle;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;

import oracle.spatial.geometry.JGeometry;
import oracle.sql.STRUCT;

import org.hibernate.HibernateException;
import org.hibernatespatial.AbstractDBGeometryType;
import org.hibernatespatial.HibernateSpatialException;

import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

/**
 * Implements Oracle 9i/10g specific geometrytype.
 * 
 * @author Karel Maesen
 */
public class SDOGeometryType extends AbstractDBGeometryType {

    private static final int[] geometryTypes = new int[] { Types.STRUCT };

    private static final GeometryFactory geomFactory = new GeometryFactory();

    private static String SQL_TYPE_NAME = "SDO_GEOMETRY";

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, sqlTypes()[0], SQL_TYPE_NAME);
        } else {
            Geometry jtsGeom = (Geometry) value;
            Object dbGeom = conv2DBGeometry(jtsGeom, st.getConnection());
            st.setObject(index, dbGeom);
        }
    }

    @Override
    public Object conv2DBGeometry(Geometry jtsGeom, Connection connection) {
        // note: we assume that there is no LRS used in jtsGeom.
        JGeometry geom = null;
        if (jtsGeom.getClass() == Point.class) {
            geom = convertJTSPoint((Point) jtsGeom);
        } else if (jtsGeom.getClass() == MultiPoint.class) {
            geom = convertJTSMultiPoint((MultiPoint) jtsGeom);
        } else if (jtsGeom.getClass() == LineString.class) {
            geom = convertJTSLineString((LineString) jtsGeom);
        } else if (jtsGeom.getClass() == LinearRing.class) {
            geom = convertJTSLinearRing((LinearRing) jtsGeom);
        } else if (jtsGeom.getClass() == MultiLineString.class) {
            geom = convertJTSMultiLineString((MultiLineString) jtsGeom);
        } else if (jtsGeom.getClass() == Polygon.class) {
            geom = convertJTSPolygon((Polygon) jtsGeom);
        } else if (jtsGeom.getClass() == MultiPolygon.class) {
            geom = convertJTSMultiPolygon((MultiPolygon) jtsGeom);
        }

        if (geom != null)
            try {
                return JGeometry.store(geom, connection);
            } catch (SQLException e) {
                throw new HibernateSpatialException(
                        "Problem during conversion from JTS to JGeometry", e);
            }
        else
            throw new UnsupportedOperationException("Conversion of "
                    + jtsGeom.getClass().getSimpleName()
                    + " to PGgeometry not supported");
    }

    private JGeometry convertJTSMultiPolygon(MultiPolygon polygon) {
        // TODO implement this
        throw new UnsupportedOperationException("Not yet implemented");
    }

    private JGeometry convertJTSPolygon(Polygon polygon) {
        int dim = getCoordDimension(polygon);
        Object[] coords = convertCoordinateArrays(
                convertGeometryToCoordArrays(polygon), dim);
        return JGeometry.createLinearPolygon(coords, dim, polygon.getSRID());
    }

    private JGeometry convertJTSMultiLineString(MultiLineString multiLineString) {
        int dim = getCoordDimension(multiLineString);
        Object[] coords = convertCoordinateArrays(
                convertGeometryToCoordArrays(multiLineString), dim);
        return JGeometry.createLinearMultiLineString(coords, dim,
                multiLineString.getSRID());
    }

    private JGeometry convertJTSLinearRing(LinearRing ring) {
        return convertJTSLineString(ring);
    }

    private JGeometry convertJTSLineString(LineString lineString) {
        int dim = getCoordDimension(lineString);
        double[] coords = convertCoordinates(lineString.getCoordinates(), dim);
        return JGeometry.createLinearLineString(coords, dim, lineString
                .getSRID());
    }

    private JGeometry convertJTSMultiPoint(MultiPoint multiPoint) {
        int dim = getCoordDimension(multiPoint);
        Object[] coords = convertCoordinateArrays(
                convertGeometryToCoordArrays(multiPoint), dim);
        return JGeometry.createMultiPoint(coords, dim, multiPoint.getSRID());
    }

    private JGeometry convertJTSPoint(Point jtsGeom) {
        int dim = getCoordDimension(jtsGeom);
        double[] coord = convertCoordinates(jtsGeom.getCoordinates(), dim);
        return JGeometry.createPoint(coord, dim, jtsGeom.getSRID());
    }

    private double[] convertCoordinates(Coordinate[] coordinates, int dim) {
        if (dim < 2 || dim > 3)
            throw new IllegalArgumentException(
                    "Dim parameter value must be between 2 and 3");
        double[] converted = new double[coordinates.length * dim];
        for (int i = 0; i < coordinates.length; i++) {
            converted[i * dim] = coordinates[i].x;
            converted[i * dim + 1] = coordinates[i].y;
            if (dim == 3)
                converted[i * dim + 2] = coordinates[i].z;
        }
        return converted;
    }

    /**
     * @param coordinateArrays
     *            array of Coordinate arrays
     * @param dim
     *            -dimension of the coordinates
     * @return
     */
    private Object[] convertCoordinateArrays(Object[] coordinateArrays, int dim) {
        Object[] converted = new Object[coordinateArrays.length];
        for (int i = 0; i < coordinateArrays.length; i++) {
            Coordinate[] coordinates = (Coordinate[]) (coordinateArrays[i]);
            converted[i] = convertCoordinates(coordinates, dim);
        }
        return converted;
    }

    private Object[] convertGeometryToCoordArrays(Geometry geom) {

        if (geom.getClass() == MultiPoint.class
                || geom.getClass() == MultiLineString.class) {
            Object[] arr = new Object[geom.getNumGeometries()];
            for (int i = 0; i < arr.length; i++) {
                arr[i] = geom.getGeometryN(i).getCoordinates();
            }
            return arr;
        } else if (geom.getClass() == Polygon.class) {
            Polygon poly = (Polygon) geom;
            Object[] arr = new Object[poly.getNumInteriorRing() + 1];
            Coordinate[] outer = poly.getExteriorRing().getCoordinates();
            // For Oracle Spatial, outer ring must be counter clockwise
            if (!CGAlgorithms.isCCW(outer))
                arr[0] = reverseRing(outer);
            else
                arr[0] = outer;

            for (int i = 0; i < poly.getNumInteriorRing(); i++) {
                Coordinate[] inner = poly.getInteriorRingN(i).getCoordinates();
                if (CGAlgorithms.isCCW(inner))
                    arr[i + 1] = reverseRing(inner);
                else
                    arr[i + 1] = inner;

            }
            return arr;
        } else
            throw new IllegalArgumentException(
                    "geom must of of type MultiPoint, MultiLineString or Polygon");
    }

    @Override
    public Geometry convert2JTS(Object geomObj) {
        if (geomObj == null)
            return null;

        JGeometry jgeom;
        try {
            jgeom = JGeometry.load((STRUCT) geomObj);
        } catch (SQLException e) {
            throw new HibernateSpatialException(
                    "Error loading Oracle Spatial Geometry Object", e);
        }

        if (jgeom.getDimensions() > 3 || jgeom.isLRSGeometry())
            throw new UnsupportedOperationException(
                    "Cannot handle LRS Geometries, or geometries with more than 3 dimensions");

        Geometry jtsGeom = null;
        if (jgeom.getType() == JGeometry.GTYPE_POINT) {
            double[] ordinates = jgeom.getOrdinatesArray();
            if (ordinates == null)
                ordinates = jgeom.getPoint();
            CoordinateSequence cs = convertOrdinateArray(jgeom
                    .getOrdinatesArray(), jgeom.getDimensions());
            jtsGeom = geomFactory.createPoint(cs);
            jtsGeom.setSRID(jgeom.getSRID());
        } else if (jgeom.getType() == JGeometry.GTYPE_MULTIPOINT) {
            CoordinateSequence cs = convertOrdinateArray(jgeom
                    .getOrdinatesArray(), jgeom.getDimensions());
            jtsGeom = geomFactory.createMultiPoint(cs);
            jtsGeom.setSRID(jgeom.getSRID());
        } else if (jgeom.getType() == JGeometry.GTYPE_CURVE) {
            ElemInfo elem = new ElemInfo();
            elem.parse(jgeom.getOrdinatesArray(), jgeom.getElemInfo(), jgeom
                    .getDimensions());
            jtsGeom = createLineString(elem, jgeom.getDimensions(), jgeom
                    .getSRID());
        } else if (jgeom.getType() == JGeometry.GTYPE_MULTICURVE) {
            ElemInfo elem = new ElemInfo();
            elem.parse(jgeom.getOrdinatesArray(), jgeom.getElemInfo(), jgeom
                    .getDimensions());
            LineString[] linestrings = new LineString[elem.getNumElems()];
            for (int i = 0; elem != null; elem = elem.getNext(), i++) {
                linestrings[i] = createLineString(elem, jgeom.getDimensions(),
                        jgeom.getSRID());
            }
            jtsGeom = geomFactory.createMultiLineString(linestrings);
            jtsGeom.setSRID(jgeom.getSRID());
        } else if (jgeom.getType() == JGeometry.GTYPE_POLYGON) {
            ElemInfo elem = new ElemInfo();
            elem.parse(jgeom.getOrdinatesArray(), jgeom.getElemInfo(), jgeom
                    .getDimensions());
            jtsGeom = createPolygon(elem, jgeom.getDimensions(), jgeom
                    .getSRID());
        } else if (jgeom.getType() == JGeometry.GTYPE_MULTIPOLYGON) {
            ElemInfo elem = new ElemInfo();
            elem.parse(jgeom.getOrdinatesArray(), jgeom.getElemInfo(), jgeom
                    .getDimensions());
            ElemInfo[] polyElems = elem.toPolyElems();
            Polygon[] polygons = new Polygon[polyElems.length];
            for (int i = 0; i < polyElems.length; i++) {
                polygons[i] = createPolygon(polyElems[i], jgeom
                        .getDimensions(), jgeom.getSRID());
            }
            jtsGeom = geomFactory.createMultiPolygon(polygons);
            jtsGeom.setSRID(jgeom.getSRID());
        } else {
            throw new IllegalArgumentException("Unsupported JGeometry type");
        }

        return jtsGeom;
    }

    private Polygon createPolygon(ElemInfo elem, int dimensions, int srid) {
        LinearRing shell = null;
        LinearRing[] holes = null;
        int idxInteriorRings = 0;
        LinearRing[] rings = new LinearRing[elem.getNumElems()];
        for (int i = 0; i < rings.length; i++) {
            if (elem.getElementType() == ElementTypes.INTERIOR_RING_ARC_SEGMENTS
                    || elem.getElementType() == ElementTypes.INTERIOR_RING_CIRCLE
                    || elem.getElementType() == ElementTypes.INTERIOR_RING_RECT
                    || elem.getElementType() == ElementTypes.INTERIOR_RING_STRAIGHT_SEGMENTS) {
                rings[idxInteriorRings] = createRing(elem, dimensions);
                rings[idxInteriorRings].setSRID(srid);
                idxInteriorRings++;
            } else {
                if (shell != null)
                    break;
                shell = createRing(elem, dimensions);
                shell.setSRID(srid);
            }
            elem = elem.getNext();
        }
        holes = new LinearRing[idxInteriorRings];
        System.arraycopy(rings, 0, holes, 0, idxInteriorRings);

        Polygon polygon = geomFactory.createPolygon(shell, holes);
        polygon.setSRID(srid);
        return polygon;
    }

    private LinearRing createRing(ElemInfo elem, int dim) {
        CoordinateSequence cs = convertOrdinateArray(elem.getOrdinates(), dim);
        // if the element is a rectangle, than complete the ring
        if (elem.getElementType() == ElementTypes.EXTERIOR_RING_RECT
                || elem.getElementType() == ElementTypes.INTERIOR_RING_RECT) {
            Coordinate ll = cs.getCoordinate(0);
            Coordinate ur = cs.getCoordinate(1);
            Coordinate lr = new Coordinate(ur.x, ll.y);
            Coordinate ul = new Coordinate(ll.x, ur.y);
            cs = geomFactory.getCoordinateSequenceFactory().create(
                    new Coordinate[] { ll, lr, ur, ul, ll });
        }

        LinearRing ring = geomFactory.createLinearRing(cs);
        return ring;
    }

    private LineString createLineString(ElemInfo elem, int dim, int SRID) {
        CoordinateSequence cs = convertOrdinateArray(elem.getOrdinates(), dim);
        LineString ls = geomFactory.createLineString(cs);
        ls.setSRID(SRID);
        return ls;
    }

    private CoordinateSequence convertOrdinateArray(double[] oordinates,
            int dim) {
        Coordinate[] coordinates = new Coordinate[oordinates.length / dim];
        for (int i = 0; i < coordinates.length; i++) {
            if (dim == 2)
                coordinates[i] = new Coordinate(oordinates[i * dim],
                        oordinates[i * dim + 1]);
            else if (dim == 3)
                coordinates[i] = new Coordinate(oordinates[i * dim],
                        oordinates[i * dim + 1], oordinates[i * dim + 2]);
        }
        return geomFactory.getCoordinateSequenceFactory().create(coordinates);
    }

    private int getCoordDimension(Geometry jtsGeom) {
        Coordinate cd = jtsGeom.getCoordinate();
        if (cd != null && !Double.isNaN(cd.z))
            return 3;
        else
            return 2;
    }

    // reverses ordinates in a coordinate array in-place
    private Coordinate[] reverseRing(Coordinate[] ar) {
        for (int i = 0; i < ar.length / 2; i++) {
            Coordinate cs = ar[i];
            ar[i] = ar[ar.length - 1 - i];
            ar[ar.length - 1 - i] = cs;
        }
        return ar;
    }

    @Override
    public int[] sqlTypes() {
        return geometryTypes;
    }

    private static class ElementTypes {
        public static int UNSUPPORTED = 0;

        public static int POINT = 1;

        public static int ORIENTATION = 2;

        public static int POINT_CLUSTER = 3;

        public static int LINE_STRAITH_SEGMENTS = 4;

        public static int LINE_ARC_SEGMENTS = 5;

        public static int INTERIOR_RING_STRAIGHT_SEGMENTS = 6;

        public static int EXTERIOR_RING_STRAIGHT_SEGMENTS = 7;

        public static int INTERIOR_RING_ARC_SEGMENTS = 8;

        public static int EXTERIOR_RING_ARC_SEGMENTS = 9;

        public static int INTERIOR_RING_RECT = 10;

        public static int EXTERIOR_RING_RECT = 11;

        public static int INTERIOR_RING_CIRCLE = 12;

        public static int EXTERIOR_RING_CIRCLE = 13;

        public static int COMPOUND_LINE = 14;

        public static int COMPOUND_EXTERIOR_RING = 15;

        public static int COMPOUND_INTERIOR_RING = 16;
    }

    /**
     * @author Karel Maesen
     * 
     * 
     * represents a simplified view on the Elements in an SDO_Geometry. Circular
     * segments are all linearized, and compound elements combined into a single
     * element with all segments linearized.
     * 
     */
    private class ElemInfo {

        private double[] linearOrdinates = null;

        private int elementType = -1;

        // used for compound elements;
        private int numCompounds = 0;

        // used for point clusters
        private int numPoints = 0;

        private ElemInfo next = null;

        protected int dimension = 2;

        protected void setDimension(int dim) {
            this.dimension = dim;
        }

        private boolean isExteriorRing() {
            if (getElementType() == ElementTypes.EXTERIOR_RING_ARC_SEGMENTS
                    || getElementType() == ElementTypes.EXTERIOR_RING_CIRCLE
                    || getElementType() == ElementTypes.EXTERIOR_RING_RECT
                    || getElementType() == ElementTypes.EXTERIOR_RING_STRAIGHT_SEGMENTS) {
                return true;
            } else
                return false;
        }

        private int getNumExteriorRings() {
            ElemInfo elem = this;
            int cnt = 0;
            while (elem != null) {
                if (elem.isExteriorRing())
                    cnt++;
                elem = elem.getNext();
            }
            return cnt;
        }

        /**
         * This splits the ElemInfo into an array of ElemInfo's, each pertaining
         * to exactly one polygon
         * 
         * @return
         */
        public ElemInfo[] toPolyElems() {
            if (getNumExteriorRings() == 0) {
                throw new IllegalStateException(
                        "toPolyElems() should only be invoked on Polygon ElemInfo's");
            } else if (getNumExteriorRings() == 1) {
                return new ElemInfo[] { this };
            } else {
                // For multipolygons, the
                //
                ElemInfo elem = this;
                ElemInfo[] polyElems = new ElemInfo[getNumExteriorRings()];
                int pIdx = 0;
                while (elem != null) {
                    if (elem.isExteriorRing()) {
                        polyElems[pIdx++] = elem;
                    }
                    elem = elem.getNext();
                }
                // strip beyond next Exterior Ring
                for (int i = 0; i < polyElems.length; i++) {
                    ElemInfo pe = polyElems[i];
                    while (pe.getNext() != null) {
                        if (pe.getNext().isExteriorRing()) {
                            pe.setNextElemInfo(null);
                            break;
                        }
                        pe = pe.getNext();
                    }
                }
                return polyElems;
            }
        }

        protected void setLinearOrdinates(double[] ordinates) {
            this.linearOrdinates = ordinates;
        }

        protected void setElementType(int type) {
            this.elementType = type;
        }

        protected void setNumCompounds(int num) {
            this.numCompounds = num;
        }

        protected void setNumPoints(int num) {
            this.numPoints = num;
        }

        protected void setNextElemInfo(ElemInfo elemInfo) {
            this.next = elemInfo;
        }

        public void parse(double[] ordinates, int[] elemInfo, int dim) {
            this.dimension = dim;
            if (ordinates.length == 0 || elemInfo.length == 0)
                return;
            int numOrdinates = 0;
            if (elemInfo[1] == 0)
                elementType = ElementTypes.UNSUPPORTED;
            else if (elemInfo[1] == 1 && elemInfo[2] == 1) {
                elementType = ElementTypes.POINT;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 1 && elemInfo[2] == 0) {
                elementType = ElementTypes.ORIENTATION;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 1 && elemInfo[2] > 1) {
                elementType = ElementTypes.POINT_CLUSTER;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numPoints = elemInfo[2];
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 2 && elemInfo[2] == 1) {
                elementType = ElementTypes.LINE_STRAITH_SEGMENTS;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 2 && elemInfo[2] == 2) {
                elementType = ElementTypes.LINE_ARC_SEGMENTS;
                double[] arcOrdinates = getElemOrdinates(ordinates, elemInfo,
                        0, dim);
                numOrdinates = arcOrdinates.length;
                linearOrdinates = linearize(arcOrdinates, dim);
            } else if (elemInfo[1] == 1003 && elemInfo[2] == 1) {
                elementType = ElementTypes.EXTERIOR_RING_STRAIGHT_SEGMENTS;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 2003 && elemInfo[2] == 1) {
                elementType = ElementTypes.INTERIOR_RING_STRAIGHT_SEGMENTS;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 1003 && elemInfo[2] == 2) {
                elementType = ElementTypes.EXTERIOR_RING_ARC_SEGMENTS;
                double[] arcOrdinates = getElemOrdinates(ordinates, elemInfo,
                        0, dim);
                numOrdinates = arcOrdinates.length;
                linearOrdinates = linearize(arcOrdinates, dim);
            } else if (elemInfo[1] == 2003 && elemInfo[2] == 2) {
                elementType = ElementTypes.INTERIOR_RING_ARC_SEGMENTS;
                double[] arcOrdinates = getElemOrdinates(ordinates, elemInfo,
                        0, dim);
                numOrdinates = arcOrdinates.length;
                linearOrdinates = linearize(arcOrdinates, dim);
            } else if (elemInfo[1] == 1003 && elemInfo[2] == 3) {
                elementType = ElementTypes.EXTERIOR_RING_RECT;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 2003 && elemInfo[2] == 3) {
                elementType = ElementTypes.INTERIOR_RING_RECT;
                linearOrdinates = getElemOrdinates(ordinates, elemInfo, 0, dim);
                numOrdinates = linearOrdinates.length;
            } else if (elemInfo[1] == 1003 && elemInfo[2] == 4) {
                elementType = ElementTypes.EXTERIOR_RING_CIRCLE;
                double[] arcOrdinates = getElemOrdinates(ordinates, elemInfo,
                        0, dim);
                numOrdinates = arcOrdinates.length;
                linearOrdinates = linearize(arcOrdinates, dim);
            } else if (elemInfo[1] == 2003 && elemInfo[2] == 4) {
                elementType = ElementTypes.INTERIOR_RING_CIRCLE;
                double[] arcOrdinates = getElemOrdinates(ordinates, elemInfo,
                        0, dim);
                numOrdinates = arcOrdinates.length;
                linearOrdinates = linearize(arcOrdinates, dim);
            } else if (elemInfo[1] == 4) {
                elementType = ElementTypes.COMPOUND_LINE;
                numCompounds = elemInfo[2];
                double[] cmpOrdinates = getElemOrdinates(ordinates, elemInfo,
                        numCompounds, dim);
                numOrdinates = cmpOrdinates.length;
                int[] cmpElemInfo = getCompoundElemInfo(elemInfo, numCompounds);
                CElemInfo celemInfo = new CElemInfo();
                celemInfo.parse(cmpOrdinates, cmpElemInfo, dim);
                linearOrdinates = celemInfo.flattenOrdinates();

            } else if (elemInfo[1] == 1005) {
                elementType = ElementTypes.COMPOUND_EXTERIOR_RING;
                numCompounds = elemInfo[2];
                double[] cmpOrdinates = getElemOrdinates(ordinates, elemInfo,
                        numCompounds, dim);
                numOrdinates = cmpOrdinates.length;
                int[] cmpElemInfo = getCompoundElemInfo(elemInfo, numCompounds);
                CElemInfo celemInfo = new CElemInfo();
                celemInfo.parse(cmpOrdinates, cmpElemInfo, dim);
                linearOrdinates = celemInfo.flattenOrdinates();
            } else if (elemInfo[1] == 1005) {
                elementType = ElementTypes.COMPOUND_INTERIOR_RING;
                numCompounds = elemInfo[2];
                double[] cmpOrdinates = getElemOrdinates(ordinates, elemInfo,
                        numCompounds, dim);
                numOrdinates = cmpOrdinates.length;
                int[] cmpElemInfo = getCompoundElemInfo(elemInfo, numCompounds);
                CElemInfo celemInfo = new CElemInfo();
                celemInfo.parse(cmpOrdinates, cmpElemInfo, dim);
                linearOrdinates = celemInfo.flattenOrdinates();
            } else
                throw new IllegalArgumentException(
                        "Can't convert ELEM_INFO to type");

            if (!(ordinates.length == numOrdinates)) {
                int[] tailElemInfo = new int[elemInfo.length
                        - (3 * (numCompounds + 1))];
                System.arraycopy(elemInfo, 3 * (numCompounds + 1),
                        tailElemInfo, 0, tailElemInfo.length);
                ordinates = getTailOrdinates(ordinates, numOrdinates);
                elemInfo = tailElemInfo;
                next = createNextElemInfo();
                next.parse(ordinates, elemInfo, dim);
            }
        }

        protected double[] getTailOrdinates(double[] ordinates,
                int numOrdinates) {
            double[] tailOrdinates = new double[ordinates.length
                    - numOrdinates];
            System.arraycopy(ordinates, numOrdinates, tailOrdinates, 0,
                    tailOrdinates.length);
            return tailOrdinates;
        }

        protected int[] getTailElemInfo(int[] elemInfo, int compounds) {
            int[] tailElemInfo = new int[elemInfo.length
                    - (3 * (numCompounds + 1))];
            System.arraycopy(elemInfo, 3 * (numCompounds + 1), tailElemInfo,
                    0, tailElemInfo.length);
            return tailElemInfo;
        }

        protected ElemInfo createNextElemInfo() {
            return new ElemInfo();
        }

        protected double[] getElemOrdinates(double[] ordinates,
                int[] elemInfo, int numCompounds, int dim) {
            int numOrdinates = 0;
            if (elemInfo.length == (3 * (numCompounds + 1))) {
                numOrdinates = ordinates.length;
                return ordinates;
            } else {
                numOrdinates = elemInfo[3 * (numCompounds + 1)] - elemInfo[0];
                double[] elemOrdinates = new double[numOrdinates];
                System.arraycopy(ordinates, 0, elemOrdinates, 0,
                        elemOrdinates.length);
                return elemOrdinates;
            }
        }

        protected int[] getCompoundElemInfo(int[] elemInfo, int numCompounds) {
            int infoArraySize = 3;
            int startOffset = 0;
            if (numCompounds > 0) {
                infoArraySize = 3 * numCompounds;
                startOffset = 3;
            }
            int[] celemInfo = new int[infoArraySize];
            System.arraycopy(elemInfo, startOffset, celemInfo, 0,
                    celemInfo.length);
            return celemInfo;
        }

        private double[] linearize(double[] arcOrdinates, int dim) {
            int numOrd = 2;
            double[] linearizedCoords = new double[0];
            // this only works with 2-Dimensional geometries, since we use
            // JGeometry linearization;
            if (dim != 2)
                throw new IllegalArgumentException(
                        "Can only linearize 2D arc segments, but geometry is "
                                + dim + "D.");
            while (numOrd < arcOrdinates.length) {
                numOrd -= 2; // start point of next arc segment is end point
                // of previous segment, and is not repeated.
                double x1 = arcOrdinates[numOrd++];
                double y1 = arcOrdinates[numOrd++];
                double x2 = arcOrdinates[numOrd++];
                double y2 = arcOrdinates[numOrd++];
                double x3 = arcOrdinates[numOrd++];
                double y3 = arcOrdinates[numOrd++];
                double[] result = JGeometry.linearizeArc(x1, y1, x2, y2, x3,
                        y3);
                // if this is not the first arcsegment, the first linearized
                // point is already in linearizedArc, so disregard this.
                int resultBegin = 2;
                if (linearizedCoords.length == 0)
                    resultBegin = 0;

                int destPos = linearizedCoords.length;
                double[] tmpCoords = new double[linearizedCoords.length
                        + result.length - resultBegin];
                System.arraycopy(linearizedCoords, 0, tmpCoords, 0,
                        linearizedCoords.length);
                System.arraycopy(result, resultBegin, tmpCoords, destPos,
                        result.length - resultBegin);
                linearizedCoords = tmpCoords;
            }

            // garuantee that first and last oordinate pairs of the
            // linearizedOrdinates are exactly the same as in input ArcOrdinates
            // This is neede because JGeometry linearization "bruises"
            // coordinates
            // which causes problems in case of linear rings.
            linearizedCoords[0] = arcOrdinates[0];
            linearizedCoords[1] = arcOrdinates[1];
            linearizedCoords[linearizedCoords.length - 1] = arcOrdinates[arcOrdinates.length - 1];
            linearizedCoords[linearizedCoords.length - 2] = arcOrdinates[arcOrdinates.length - 2];

            return linearizedCoords;
        }

        public double[] getOrdinates() {
            return linearOrdinates;
        }

        public int getElementType() {
            return elementType;
        }

        public int getNumCompounts() {
            return numCompounds;
        }

        public int getNumPoints() {
            return numPoints;
        }

        public ElemInfo getNext() {
            return next;
        }

        public int getNumElems() {
            int cnt = 0;
            for (ElemInfo elem = this; elem != null; elem = elem.getNext(), cnt++)
                ;
            return cnt;
        }

    }

    private class CElemInfo extends ElemInfo {

        public CElemInfo() {
            super();
        }

        protected ElemInfo createNextElemInfo() {
            return new CElemInfo();
        }

        // subelements of a compound element are contiguous. Thefore
        // we include the first coordinate of the next subelement.
        protected double[] getElemOrdinates(double[] ordinates,
                int[] elemInfo, int numCompounds, int dim) {
            if (elemInfo.length == (3 * (numCompounds + 1))) {
                return ordinates;
            } else {
                // numOrdinates includes first ordinate of next subelement, if
                // there is one
                int numOrdinates = elemInfo[3 * (numCompounds + 1)]
                        - elemInfo[0] + dim;
                double[] elemOrdinates = new double[numOrdinates];
                System.arraycopy(ordinates, 0, elemOrdinates, 0,
                        elemOrdinates.length);
                return elemOrdinates;
            }
        }

        protected double[] getTailOrdinates(double[] ordinates,
                int numOrdinates) {
            double[] tailOrdinates = new double[ordinates.length
                    - numOrdinates + this.dimension];
            if (numOrdinates < ordinates.length)
                System.arraycopy(ordinates, numOrdinates - this.dimension,
                        tailOrdinates, 0, tailOrdinates.length);
            else
                System.arraycopy(ordinates, numOrdinates, tailOrdinates, 0,
                        tailOrdinates.length);
            return tailOrdinates;
        }

        protected double[] flattenOrdinates() {
            ElemInfo elem = this;
            double[] ordinates = new double[elem.getOrdinates().length];
            System.arraycopy(elem.getOrdinates(), 0, ordinates, 0, this
                    .getOrdinates().length);
            int nextStartPos = ordinates.length;
            while (elem.getNext() != null) {
                elem = elem.getNext();
                int nextOrdinateArraySize = elem.getOrdinates().length
                        - dimension;
                double[] newArray = new double[ordinates.length
                        + nextOrdinateArraySize];
                System.arraycopy(ordinates, 0, newArray, 0, ordinates.length);
                System.arraycopy(elem.getOrdinates(), dimension, newArray,
                        nextStartPos, nextOrdinateArraySize);
                ordinates = newArray;
                nextStartPos = ordinates.length;
            }
            return ordinates;
        }

    }

}