/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * 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; * version 2.1 of the License. * * 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. * */ package org.geotools.arcsde.data; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.arcsde.ArcSdeException; import org.geotools.data.DataSourceException; import org.geotools.geometry.jts.LiteCoordinateSequenceFactory; import org.geotools.util.logging.Logging; import com.esri.sde.sdk.client.SDEPoint; import com.esri.sde.sdk.client.SeCoordinateReference; import com.esri.sde.sdk.client.SeException; import com.esri.sde.sdk.client.SeShape; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.CoordinateSequenceFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; 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; /** * Creates propper JTS Geometry objects from <code>SeShape</code> objects and viceversa. * <p> * <code>SeShape</code>'s are gathered from an <code>SeRow</code> ArcSDE API's result object and * holds it's geometry attributes as a three dimensional array of <code>double</code> primitives as * explained bellow. * </p> * <p> * By this way, we avoid the creation of ArcSDE's java implementation of OGC geometries for later * translation to JTS, avoiding too the dependency on the ArcSDE native library wich the geometry * package of the ArcSDE Java API depends on. * </p> * <p> * Given <code>double [][][]coords</code> the meaning of this array is as follow: * <ul> * <li>coords.length reprsents the number of geometries this geometry is composed of. In deed, this * only applies for multipolygon geometries, for all other geometry types, this will be allways * <code>1</code></li> * <li>coords[n] holds the coordinate arrays of the n'th geometry this geometry is composed of. * Except for multipolygons, this will allways be <code>coords[0]</code>.</li> * <li>coords[n][m] holds the coordinates array for a given geometry. (i.e. [0][m] for a * multilinestring or [2][m] for a multipolygon composed of 3 polygons)</li> * <li>coords[n][m][l] holds the {x1, y1, x2, y2, ...,Xn, Yn} coordinates for a given geometry part</li> * </ul> * </p> * <p> * This abstract class will use specialized subclass for constructing the propper geometry type * </p> * * @author Gabriel Roldan, Axios Engineering * * @source $URL$ * http://svn.geotools.org/geotools/trunk/gt/modules/plugin/arcsde/datastore/src/main/java * /org/geotools/arcsde/data/ArcSDEGeometryBuilder.java $ * @version $Id$ */ public abstract class ArcSDEGeometryBuilder { private static final Logger LOGGER = Logging.getLogger(ArcSDEGeometryBuilder.class.getName()); /** lookup specialized geometry builders classes by it's geometry type */ private static final Map<Class<?>, ArcSDEGeometryBuilder> builders = new HashMap<Class<?>, ArcSDEGeometryBuilder>(); /** Look up "empty" geometry instances based on geometry class */ private static final Map<Class<?>, Geometry> nullGeometries = new HashMap<Class<?>, Geometry>(); static { builders.put(Geometry.class, GenericGeometryBuilder.getInstance()); builders.put(GeometryCollection.class, GenericGeometryBuilder.getInstance()); builders.put(Point.class, PointBuilder.getInstance()); builders.put(MultiPoint.class, MultiPointBuilder.getInstance()); builders.put(LineString.class, LineStringBuilder.getInstance()); builders.put(MultiLineString.class, MultiLineStringBuilder.getInstance()); builders.put(Polygon.class, PolygonBuilder.getInstance()); builders.put(MultiPolygon.class, MultiPolygonBuilder.getInstance()); nullGeometries.put(Geometry.class, new GenericGeometryBuilder().getEmpty()); nullGeometries.put(Point.class, new PointBuilder().getEmpty()); nullGeometries.put(MultiPoint.class, new MultiPointBuilder().getEmpty()); nullGeometries.put(LineString.class, new LineStringBuilder().getEmpty()); nullGeometries.put(MultiLineString.class, new MultiLineStringBuilder().getEmpty()); nullGeometries.put(Polygon.class, new PolygonBuilder().getEmpty()); nullGeometries.put(MultiPolygon.class, new MultiPolygonBuilder().getEmpty()); } /** * Private empty constructor to obligate using this class as factory. */ private ArcSDEGeometryBuilder() { // intentionally blank } /** * Takes an ArcSDE's <code>SeShape</code> and builds a JTS Geometry. The geometry type * constructed depends on this <code>ArcSDEGeometryBuilder</code> specialized subclass * * @param shape * the ESRI's ArcSDE java api shape upon wich to create the new JTS geometry * @param geometryFactory * @return the type of JTS Geometry this subclass instance is specialized for or an empty * geometry of the same class if <code>shape.isNil()</code> * @throws SeException * if it occurs fetching the coordinates array from <code>shape</code> * @throws DataSourceException * if the <code>com.vividsolutions.jts.geom.GeometryFactory</code> this builder is * backed by can't create the <code>com.vividsolutions.jts.geom.Geometry</code> with * the <code>com.vividsolutions.jts.geom.Coordinate[]</code> provided by * <code>newGeometry</code> */ public Geometry construct(final SeShape shape, final GeometryFactory geometryFactory) throws SeException, DataSourceException { if (shape == null || shape.isNil()) { return getEmpty(); } double[][][] allCoords = shape.getAllCoords(); return newGeometry(allCoords, geometryFactory); } /** * Creates the ArcSDE Java API representation of a <code>Geometry</code> object in its shape * format, suitable to filter expressions as the SDE API expects * * @param geometry * the JTS Geometry object to get the SDE representation from * @param seSrs * Coordinate Reference System of the underlying <code>SeLayer</code> object for wich * the <code>SeShape</code> is constructed. * @return the <code>SeShape</code> representation of passed <code>Geometry</code> * @throws ArcSDEGeometryBuildingException */ public final SeShape constructShape(final Geometry geometry, SeCoordinateReference seSrs) throws ArcSdeException { if (geometry == null) { return null; } SeShape shape = null; try { shape = new SeShape(seSrs); } catch (SeException ex) { ArcSdeException e = new ArcSdeException("Can't create SeShape with SeCrs " + seSrs, ex); LOGGER.log(Level.WARNING, e.getMessage(), e); throw e; } if (geometry.isEmpty()) { return shape; } // REVISIT: this may be worth considering. If not, at least shape.generateFromWKB // final String wkt = geometry.toText(); // try { // shape.generateFromText(wkt); // } catch (SeException e) { // ArcSdeException sdeEx = new ArcSdeException("Can't generate SeShape from " + geometry // + "\n", e); // LOGGER.log(Level.WARNING, sdeEx.getMessage()); // throw sdeEx; // } int numParts; GeometryCollection gcol = null; if (geometry instanceof GeometryCollection) { gcol = (GeometryCollection) geometry; } else { Geometry[] geoms = { geometry }; gcol = new GeometryFactory().createGeometryCollection(geoms); } List<SDEPoint> allPoints = new ArrayList<SDEPoint>(); numParts = gcol.getNumGeometries(); int[] partOffsets = new int[numParts]; Geometry geom; Coordinate[] coords; Coordinate c; for (int currGeom = 0; currGeom < numParts; currGeom++) { partOffsets[currGeom] = allPoints.size(); geom = gcol.getGeometryN(currGeom); coords = geom.getCoordinates(); for (int i = 0; i < coords.length; i++) { c = coords[i]; allPoints.add(new SDEPoint(c.x, c.y)); } } SDEPoint[] points = new SDEPoint[allPoints.size()]; allPoints.toArray(points); try { if (geometry instanceof Point || gcol instanceof MultiPoint) { shape.generatePoint(points.length, points); } else if (geometry instanceof LineString || geometry instanceof MultiLineString) { shape.generateLine(points.length, numParts, partOffsets, points); } else { shape.generatePolygon(points.length, numParts, partOffsets, points); } } catch (SeException e) { ArcSdeException sdeEx = new ArcSdeException("Can't generate SeShape from " + geometry + "\n", e); LOGGER.log(Level.WARNING, sdeEx.getMessage()); throw sdeEx; } return shape; } /** * Builds a JTS Geometry who't type is given by the <code>ArcSDEGeometryBuilder</code> subclass * instance specialization that implements it * * @param coords * <code>SeShape</code>'s coordinate array to build the geometry from * @param geometryFactory * @return the JST form of the passed geometry coordinates * @throws DataSourceException * if an error occurs while creating the JTS Geometry */ protected abstract Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException; /** * returns an empty JTS geometry who's type is given by the <code>ArcSDEGeometryBuilder</code> * subclass instance specialization that implements it. * <p> * this method is called in case that <code>SeShape.isNil() == true</code> * </p> * * @return an empty JTS geometry * @throws UnsupportedOperationException */ protected Geometry getEmpty() { throw new UnsupportedOperationException( "this method sholdn't be called directly, it's intended pourpose" + " is to be implemented by subclasses so they provide propper " + " null Geometries"); } /** * Builds an array of JTS <code>Coordinate</code> instances that's geometrically equals to the * <code>SeShape</code> single coordinates array passed as argument. * * @param coordList * array of coordinates of a single shape part to build a <code>Coordinate</code> * from * @return a geometrically equal to <code>coordList</code> array of <code>Coordinate</code> * instances */ protected final CoordinateSequence toCoords(double[] coordList, final CoordinateSequenceFactory csFact) { final int dimension = 2; CoordinateSequence cs; if (csFact instanceof LiteCoordinateSequenceFactory) { cs = ((LiteCoordinateSequenceFactory) csFact).create(coordList, dimension); } else { final int nCoords = coordList.length / dimension; cs = csFact.create(nCoords, dimension); for (int coordN = 0; coordN < nCoords; coordN++) { cs.setOrdinate(coordN, 0, coordList[dimension * coordN]); cs.setOrdinate(coordN, 1, coordList[dimension * coordN + 1]); } } return cs; } protected SDEPoint[] toPointsArray(Coordinate[] coords) { int nCoords = coords.length; SDEPoint[] points = new SDEPoint[nCoords]; Coordinate c; for (int i = 0; i < nCoords; i++) { c = coords[i]; points[i] = new SDEPoint(c.x, c.y); } return points; } /** * Factory method that returns an instance of <code>ArcSDEGeometryBuilder</code> specialized in * contructing JTS geometries of the JTS Geometry class passed as argument. Note that * <code>jtsGeometryClass</code> must be one of the supported concrete JTS Geometry classes. * * @param jtsGeometryClass * @throws IllegalArgumentException * if <code>jtsGeometryClass</code> is not a concrete JTS <code>Geometry</code> * class (like <code>com.vividsolutions.jts.geom.MultiPoint.class</code> i.e.) */ public static ArcSDEGeometryBuilder builderFor(Class<? extends Geometry> jtsGeometryClass) throws IllegalArgumentException { ArcSDEGeometryBuilder builder = (ArcSDEGeometryBuilder) builders.get(jtsGeometryClass); if (builder == null) { String msg = "no geometry builder is defined to construct " + jtsGeometryClass + " instances."; throw new IllegalArgumentException(msg); } return builder; } /** * Create an empty geometry for the indicated class * */ public static Geometry defaultValueFor(Class<?> geoClass) { if (geoClass == null) { throw new NullPointerException("got null geometry class"); } Geometry emptyGeom = (Geometry) nullGeometries.get(geoClass); return emptyGeom; } /** * <code>ArcSDEGeometryBuilder</code> which can create any type of JTS geometry from * <code>SeShape</code>'s and viceversa * * @author Gabriel Roldan, Axios Engineering * @version $Id$ */ private static class GenericGeometryBuilder extends ArcSDEGeometryBuilder { private static Geometry EMPTY; /** singleton for generic geometry building */ private static final ArcSDEGeometryBuilder instance = new GenericGeometryBuilder(); /** * Returns an instance of this geometry builder. Currently implemented as a singleton since * it is completely thread safe. * * @return the <code>GenericGeometryBuilder</code> singleton. */ public static ArcSDEGeometryBuilder getInstance() { return instance; } @Override protected Geometry getEmpty() { if (EMPTY == null) { EMPTY = new GeometryFactory().createGeometryCollection(new Geometry[] {}); } return EMPTY; } /** * @param shape * the shape to create its JTS geometry equivalent. Can't be null. * @see ArcSDEGeometryBuilder#construct(SeShape) */ @Override public Geometry construct(SeShape shape, final GeometryFactory geometryFactory) throws SeException, DataSourceException { if (shape == null || shape.isNil()) { return getEmpty(); } Class<? extends Geometry> realGeomClass = ArcSDEAdapter .getGeometryTypeFromSeShape(shape); if (realGeomClass == null) { return null; } ArcSDEGeometryBuilder realBuilder = builderFor(realGeomClass); return realBuilder.construct(shape, geometryFactory); } @Override protected Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException { throw new UnsupportedOperationException("This method should not " + "be called for this builder. It should be mapped to the " + "one capable of constructing the actual geometry type"); } } /** * <code>ArcSDEGeometryBuilder</code> specialized in creating JTS <code>Point</code> s from * <code>SeShape</code> points and viceversa * * @author Gabriel Roldan, Axios Engineering * @version $Id$ */ private static class PointBuilder extends ArcSDEGeometryBuilder { /** the empty point singleton */ private static Geometry EMPTY; /** singleton for point building */ private static final ArcSDEGeometryBuilder instance = new PointBuilder(); /** * Returns an instance of this geometry builder for Point geometries. Currently implemented * as a singleton since it is completely thread safe. * * @return the <code>PointBuilder</code> singleton. */ public static ArcSDEGeometryBuilder getInstance() { return instance; } @Override protected Geometry getEmpty() { if (EMPTY == null) { EMPTY = new GeometryFactory().createPoint((Coordinate) null); } return EMPTY; } @Override protected Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException { final double x = coords[0][0][0]; final double y = coords[0][0][1]; return geometryFactory.createPoint(new Coordinate(x, y)); } } /** * <code>ArcSDEGeometryBuilder</code> specialized in creating JTS <code>MultiPoint</code> s from * <code>SeShape</code> multipoints and viceversa * * @author Gabriel Roldan, Axios Engineering * @version $Id$ */ private static class MultiPointBuilder extends ArcSDEGeometryBuilder { /** the empty multipoint singleton */ private static Geometry EMPTY; /** singleton for multipoint building */ private static final ArcSDEGeometryBuilder instance = new MultiPointBuilder(); /** * Returns an instance of this geometry builder for MultiPoint geometries. Currently * implemented as a singleton since it is completely thread safe. * * @return the <code>MultiPointBuilder</code> singleton. */ public static ArcSDEGeometryBuilder getInstance() { return instance; } @Override protected Geometry getEmpty() { if (EMPTY == null) { EMPTY = new GeometryFactory().createMultiPoint((Point[]) null); } return EMPTY; } @Override protected Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException { int nPoints = coords.length; Coordinate[] points = new Coordinate[nPoints]; for (int i = 0; i < nPoints; i++) { double x = coords[i][0][0]; double y = coords[i][0][1]; points[i] = new Coordinate(x, y); } return geometryFactory.createMultiPoint(points); } } /** * <code>ArcSDEGeometryBuilder</code> specialized in creating JTS <code>LineString</code> s from * <code>SeShape</code> linestring and viceversa * * @author Gabriel Roldan, Axios Engineering * @version $Id$ */ private static class LineStringBuilder extends ArcSDEGeometryBuilder { /** the empty linestring singleton */ private static Geometry EMPTY; /** singleton for linestring building */ private static final ArcSDEGeometryBuilder instance = new LineStringBuilder(); /** * Returns an instance of this geometry builder for LineString geometries. Currently * implemented as a singleton since it is completely thread safe. * * @return the <code>LineStringBuilder</code> singleton. */ public static ArcSDEGeometryBuilder getInstance() { return instance; } @Override protected Geometry getEmpty() { if (EMPTY == null) { EMPTY = new GeometryFactory().createLineString((Coordinate[]) null); } return EMPTY; } @Override protected Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException { return constructLineString(coords[0][0], geometryFactory); } protected final LineString constructLineString(final double[] linearCoords, final GeometryFactory geometryFactory) throws DataSourceException { LineString ls = null; CoordinateSequence coords = toCoords(linearCoords, geometryFactory.getCoordinateSequenceFactory()); ls = geometryFactory.createLineString(coords); return ls; } } /** * <code>ArcSDEGeometryBuilder</code> specialized in creating JTS <code>MultiLineString</code> s * from <code>SeShape</code> multilinestrings and viceversa * * @author Gabriel Roldan, Axios Engineering * @version $Id$ */ private static class MultiLineStringBuilder extends LineStringBuilder { /** the empty multilinestring singleton */ private static Geometry EMPTY; /** singleton for multilinestring building */ private static final ArcSDEGeometryBuilder instance = new MultiLineStringBuilder(); /** * Returns an instance of this geometry builder for MultiLineString geometries. Currently * implemented as a singleton since it is completely thread safe. * * @return the <code>MultiLineStringBuilder</code> singleton. */ public static ArcSDEGeometryBuilder getInstance() { return instance; } @Override protected Geometry getEmpty() { if (EMPTY == null) { EMPTY = new GeometryFactory().createMultiLineString(null); } return EMPTY; } @Override protected Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException { MultiLineString mls = null; LineString[] lineStrings = null; int nLines = coords.length; lineStrings = new LineString[nLines]; for (int i = 0; i < nLines; i++) { lineStrings[i] = constructLineString(coords[i][0], geometryFactory); } mls = geometryFactory.createMultiLineString(lineStrings); return mls; } } /** * <code>ArcSDEGeometryBuilder</code> specialized in creating JTS <code>Polygon</code> s from * <code>SeShape</code> polygon and viceversa * * @author Gabriel Roldan, Axios Engineering * @version $Id$ */ private static class PolygonBuilder extends ArcSDEGeometryBuilder { /** the empty polygon singleton */ private static Geometry EMPTY; /** singleton for polygon building */ private static final ArcSDEGeometryBuilder instance = new PolygonBuilder(); /** * Returns an instance of this geometry builder for Polygon geometries. Currently * implemented as a singleton since it is completely thread safe. * * @return the <code>PolygonBuilder</code> singleton. */ public static ArcSDEGeometryBuilder getInstance() { return instance; } @Override protected Geometry getEmpty() { if (EMPTY == null) { EMPTY = new GeometryFactory().createPolygon(null, null); } return EMPTY; } @Override protected Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException { // ///// /* * for (int i = 0; i < coords.length; i++) { for (int j = 0; j < coords[i].length; j++) * { double[] ds = coords[i][j]; //System.out.println("coords[" + i + "][" + j + "]=" + * Arrays.toString(ds)); } } //System.out.println("-----"); */ // /////// double[] shell = coords[0][0]; int nParts = coords[0].length; int nHoles = nParts - 1; double[][] holes = new double[nHoles][1]; for (int i = 0; i < nHoles; i++) { holes[i] = coords[0][i + 1]; } return buildPolygon(shell, holes, geometryFactory); } protected final Polygon buildPolygon(final double[] shellCoords, final double[][] holes, final GeometryFactory geometryFactory) { Polygon p = null; final CoordinateSequenceFactory sequenceFactory = geometryFactory .getCoordinateSequenceFactory(); final CoordinateSequence coords = toCoords(shellCoords, sequenceFactory); final LinearRing shell = geometryFactory.createLinearRing(coords); final int nHoles = holes.length; LinearRing[] polygonHoles = new LinearRing[nHoles]; if (nHoles > 0) { for (int i = 0; i < nHoles; i++) { double hole[] = holes[i]; polygonHoles[i] = geometryFactory.createLinearRing(toCoords(hole, sequenceFactory)); } } p = geometryFactory.createPolygon(shell, polygonHoles); return p; } @Deprecated protected Polygon buildPolygon(final double[][] parts, final GeometryFactory geometryFactory) { Polygon p = null; double[] linearCoordArray = parts[0]; int nHoles = parts.length - 1; final CoordinateSequenceFactory coordinateSequenceFactory = geometryFactory .getCoordinateSequenceFactory(); LinearRing shell = geometryFactory.createLinearRing(toCoords(linearCoordArray, coordinateSequenceFactory)); LinearRing[] holes = new LinearRing[nHoles]; if (nHoles > 0) { for (int i = 0; i < nHoles; i++) { linearCoordArray = parts[i + 1]; holes[i] = geometryFactory.createLinearRing(toCoords(linearCoordArray, coordinateSequenceFactory)); } } p = geometryFactory.createPolygon(shell, holes); return p; } } /** * <code>ArcSDEGeometryBuilder</code> specialized in creating JTS <code>MultiPolygon</code> s * from <code>SeShape</code> multipolygons and viceversa * * @author Gabriel Roldan, Axios Engineering * @version $Id$ */ private static class MultiPolygonBuilder extends PolygonBuilder { /** the empty multipolygon singleton */ private static Geometry EMPTY; /** singleton for multipolygon building */ private static final ArcSDEGeometryBuilder instance = new MultiPolygonBuilder(); /** * Returns an instance of this geometry builder for MultiPolygon geometries. Currently * implemented as a singleton since it is completely thread safe. * * @return the <code>PointBuilder</code> singleton. */ public static ArcSDEGeometryBuilder getInstance() { return instance; } @Override protected Geometry getEmpty() { if (EMPTY == null) { EMPTY = new GeometryFactory().createMultiPolygon(null); } return EMPTY; } /** * * @param coords * the SeShape's multipolygon coordinates array * @return a <code>MultiPolygon</code> constructed based on the SDE shape, or the empty * geometry if the <code>shape == null || * shape.isNil()</code> * @throws DataSourceException * if it is not possible to obtain the shape's coordinate arrays or an exception * occurs while building the Geometry */ @Override protected Geometry newGeometry(final double[][][] coords, final GeometryFactory geometryFactory) throws DataSourceException { Polygon[] polys = null; int numPolys = coords.length; polys = new Polygon[numPolys]; for (int i = 0; i < numPolys; i++) { try { polys[i] = buildPolygon(coords[i], geometryFactory); } catch (Exception ex) { throw new DataSourceException(ex.getMessage(), ex); } } MultiPolygon multiPoly = geometryFactory.createMultiPolygon(polys); return multiPoly; } } }