/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.function;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.PushbackReader;
import java.io.Reader;
import java.sql.Blob;
import java.sql.SQLException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.teiid.UserDefinedAggregate;
import org.teiid.core.types.BlobImpl;
import org.teiid.core.types.BlobType;
import org.teiid.core.types.ClobImpl;
import org.teiid.core.types.ClobType;
import org.teiid.core.types.ClobType.Type;
import org.teiid.core.types.GeometryType;
import org.teiid.core.types.InputStreamFactory;
import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version;
import org.teiid.query.util.CommandContext;
import org.teiid.runtime.client.Messages;
import org.teiid.runtime.client.TeiidClientException;
import org.wololo.geojson.GeoJSON;
import org.wololo.jts2geojson.GeoJSONReader;
import org.wololo.jts2geojson.GeoJSONWriter;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.io.ByteOrderValues;
import com.vividsolutions.jts.io.InputStreamInStream;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.gml2.GMLHandler;
import com.vividsolutions.jts.io.gml2.GMLWriter;
import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier;
/**
* Utility methods for geometry
* TODO: determine if we should use buffer manager to minimize memory footprint
* TODO: Split into GeometryFilterUtils and GeometryConvertUtils. - Tom
*/
public class GeometryUtils {
private static final int SRID_4326 = 4326;
public static ClobType geometryToClob(GeometryType geometry,
boolean withSrid)
throws Exception {
Geometry jtsGeometry = getGeometry(geometry);
int srid = jtsGeometry.getSRID();
StringBuilder geomText = new StringBuilder();
if (withSrid && srid != GeometryType.UNKNOWN_SRID) {
geomText.append("SRID=").append(jtsGeometry.getSRID()).append(";"); //$NON-NLS-1$ //$NON-NLS-2$
}
geomText.append(jtsGeometry.toText());
return new ClobType(new ClobImpl(geomText.toString()));
}
public static GeometryType geometryFromClob(ClobType wkt)
throws Exception {
return geometryFromClob(wkt, GeometryType.UNKNOWN_SRID, false);
}
public static GeometryType geometryFromClob(ClobType wkt, Integer srid, boolean allowEwkt)
throws Exception {
Reader r = null;
try {
WKTReader reader = new WKTReader();
r = wkt.getCharacterStream();
if (allowEwkt) {
PushbackReader pbr = new PushbackReader(r, 1);
r = pbr;
char[] expected = new char[] {'s', 'r', 'i', 'd','='};
int expectedIndex = 0;
StringBuilder sridBuffer = null;
for (int i = 0; i < 100000; i++) {
int charRead = pbr.read();
if (charRead == -1) {
break;
}
if (expectedIndex == expected.length) {
//parse srid
if (sridBuffer == null) {
sridBuffer = new StringBuilder(4);
}
if (charRead == ';') {
if (sridBuffer.length() == 0) {
pbr.unread(charRead);
}
break;
}
sridBuffer.append((char)charRead);
continue;
}
if (expectedIndex == 0 && Character.isWhitespace(charRead)) {
continue;
}
if (expected[expectedIndex] != Character.toLowerCase(charRead)) {
pbr.unread(charRead);
break;
}
expectedIndex++;
}
if (sridBuffer != null) {
srid = Integer.parseInt(sridBuffer.toString());
}
}
Geometry jtsGeometry = reader.read(r);
if (!allowEwkt && (jtsGeometry.getSRID() != GeometryType.UNKNOWN_SRID || (jtsGeometry.getCoordinate() != null && !Double.isNaN(jtsGeometry.getCoordinate().z)))) {
//don't allow ewkt that requires a specific function
throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID31160, "EWKT"));
}
if (srid == null) {
srid = jtsGeometry.getSRID();
}
return getGeometryType(jtsGeometry, srid);
} catch (Exception e) {
throw new TeiidClientException(e);
} finally {
if (r != null) {
try {
r.close();
} catch (IOException e) {
}
}
}
}
public static ClobType geometryToGeoJson(GeometryType geometry)
throws Exception {
Geometry jtsGeometry = getGeometry(geometry);
GeoJSONWriter writer = new GeoJSONWriter();
try {
GeoJSON geoJson = writer.write(jtsGeometry);
ClobType result = new ClobType(new ClobImpl(geoJson.toString()));
result.setType(Type.JSON);
return result;
} catch (Exception e) {
throw new Exception(e);
}
}
public static GeometryType geometryFromGeoJson(ClobType json)
throws Exception {
return geometryFromGeoJson(json, GeometryType.UNKNOWN_SRID);
}
public static GeometryType geometryFromGeoJson(ClobType json, int srid)
throws Exception {
try {
GeoJSONReader reader = new GeoJSONReader();
String jsonText = ClobType.getString(json);
Geometry jtsGeometry = reader.read(jsonText);
return getGeometryType(jtsGeometry, srid);
} catch (SQLException e) {
throw new Exception(e);
} catch (IOException e) {
throw new Exception(e);
}
}
public static ClobType geometryToGml(CommandContext ctx, GeometryType geometry,
boolean withGmlPrefix)
throws Exception {
Geometry jtsGeometry = getGeometry(geometry);
GMLWriter writer = new GMLWriter();
if (!withGmlPrefix) {
if (geometry.getSrid() != SRID_4326) {
if (geometry.getSrid() == GeometryType.UNKNOWN_SRID) {
throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID31161));
}
jtsGeometry = GeometryTransformUtils.transform(ctx, jtsGeometry, SRID_4326);
}
writer.setPrefix(null);
} else if (geometry.getSrid() != GeometryType.UNKNOWN_SRID) {
//TODO: should include the srsName
//writer.setSrsName(String.valueOf(geometry.getSrid()));
}
String gmlText = writer.write(jtsGeometry);
return new ClobType(new ClobImpl(gmlText));
}
public static GeometryType geometryFromGml(ClobType gml, Integer srid)
throws Exception {
try {
return geometryFromGml(gml.getCharacterStream(), srid);
} catch (SQLException e) {
throw new Exception(e);
}
}
/**
* Custom SAX handler extending GMLHandler to handle parsing SRIDs.
*
* The default JTS logic only handles srsName=int or srsName=uri/int
* whereas other systems commonly use srsName=name:int
*/
private static class GmlSridHandler extends GMLHandler {
private int srid = GeometryType.UNKNOWN_SRID;
public GmlSridHandler(GeometryFactory gf, ErrorHandler delegate) {
super(gf, delegate);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
String srsName = attributes.getValue("srsName"); //$NON-NLS-1$
if (srsName != null) {
String[] srsParts = srsName.split(":"); //$NON-NLS-1$
try {
if (srsParts.length == 2) {
srid = Integer.parseInt(srsParts[1]);
}
} catch (NumberFormatException e) {
// ignore
}
}
super.startElement(uri, localName, qName, attributes);
}
public int getSrid() {
return srid;
}
}
public static GeometryType geometryFromGml(Reader reader, Integer srid)
throws Exception {
GeometryFactory gf = new GeometryFactory();
Geometry jtsGeometry = null;
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(false);
factory.setValidating(false);
SAXParser parser = factory.newSAXParser();
GmlSridHandler handler = new GmlSridHandler(gf, null);
parser.parse(new InputSource(reader), handler);
jtsGeometry = handler.getGeometry();
if (srid == null) {
if (jtsGeometry.getSRID() == GeometryType.UNKNOWN_SRID) {
srid = handler.getSrid();
} else {
srid = jtsGeometry.getSRID();
}
}
} catch (IOException e) {
throw new Exception(e);
} catch (SAXException e) {
throw new Exception(e);
} catch (ParserConfigurationException e) {
throw new Exception(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
// Nothing
}
}
}
return getGeometryType(jtsGeometry, srid);
}
public static GeometryType geometryFromBlob(BlobType wkb)
throws Exception {
return geometryFromBlob(wkb, GeometryType.UNKNOWN_SRID);
}
//TODO: should allow an option to assume well formed
public static GeometryType geometryFromBlob(BlobType wkb, int srid) throws Exception {
//return as geometry
GeometryType gt = new GeometryType(wkb.getReference(), srid);
//validate
getGeometry(gt);
return gt;
}
public static Boolean intersects(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.intersects(g2);
}
public static Boolean contains(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.contains(g2);
}
public static Boolean disjoint(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.disjoint(g2);
}
public static Boolean crosses(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.crosses(g2);
}
public static Double distance(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.distance(g2);
}
public static Boolean touches(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.touches(g2);
}
public static Boolean overlaps(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.overlaps(g2);
}
public static GeometryType getGeometryType(Geometry jtsGeom) {
return getGeometryType(jtsGeom, jtsGeom.getSRID());
}
public static GeometryType getGeometryType(Geometry jtsGeom, int srid) {
WKBWriter writer = new WKBWriter();
byte[] bytes = writer.write(jtsGeom);
return new GeometryType(bytes, srid);
}
public static Geometry getGeometry(GeometryType geom)
throws Exception {
try {
return getGeometry(geom.getBinaryStream(), geom.getSrid(), false);
} catch (SQLException e) {
throw new Exception(e);
}
}
public static Geometry getGeometry(InputStream is1, Integer srid, boolean allowEwkb)
throws Exception {
try {
WKBReader reader = new WKBReader();
Geometry jtsGeom = reader.read(new InputStreamInStream(is1));
if (!allowEwkb && (jtsGeom.getSRID() != GeometryType.UNKNOWN_SRID || (jtsGeom.getCoordinate() != null && !Double.isNaN(jtsGeom.getCoordinate().z)))) {
//don't allow ewkb - that needs an explicit function
throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID31160, "EWKB"));
}
if (srid != null) {
jtsGeom.setSRID(srid);
}
return jtsGeom;
} catch (Exception e) {
throw new TeiidClientException(e);
} finally {
if (is1 != null) {
try {
is1.close();
} catch (IOException e) {
}
}
}
}
public static Boolean equals(GeometryType geom1, GeometryType geom2) throws Exception {
return getGeometry(geom1).equalsTopo(getGeometry(geom2));
}
public static GeometryType geometryFromEwkb(InputStream is, Integer srid) throws Exception {
Geometry geom = getGeometry(is, srid, true);
return getGeometryType(geom);
}
public static GeometryType simplify(
GeometryType geom, double tolerance) throws Exception {
return getGeometryType(DouglasPeuckerSimplifier.simplify(getGeometry(geom), tolerance));
}
public static boolean boundingBoxIntersects(
GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.getEnvelope().intersects(g2.getEnvelope());
}
public static GeometryType envelope(GeometryType geom) throws Exception {
return getGeometryType(getGeometry(geom).getEnvelope());
}
public static Boolean within(GeometryType geom1, GeometryType geom2) throws Exception {
Geometry g1 = getGeometry(geom1);
Geometry g2 = getGeometry(geom2);
return g1.within(g2);
}
public static Boolean dwithin(GeometryType geom1, GeometryType geom2, double distance) throws Exception {
return distance(geom1, geom2) < distance;
}
public static class Extent implements UserDefinedAggregate<GeometryType> {
private Geometry g;
public Extent() {
}
@Override
public void reset() {
g = null;
}
public void addInput(GeometryType geom) throws Exception {
Geometry g1 = getGeometry(geom);
if (g == null) {
g = g1.getEnvelope();
} else {
g = g.union(g1.getEnvelope());
}
}
@Override
public GeometryType getResult(org.teiid.CommandContext commandContext) {
if (g == null) {
return null;
}
return getGeometryType(g.getEnvelope());
}
}
/**
* We'll take the wkb format and add the extended flag/srid
* @param geometry
* @return
*/
public static BlobType geometryToEwkb(final GeometryType geometry) {
final Blob b = geometry.getReference();
BlobImpl blobImpl = new BlobImpl(new InputStreamFactory() {
@Override
public InputStream getInputStream() throws IOException {
PushbackInputStream pbis;
try {
pbis = new PushbackInputStream(b.getBinaryStream(), 9);
} catch (SQLException e) {
throw new IOException(e);
}
int byteOrder = pbis.read();
if (byteOrder == -1) {
return pbis;
}
byte[] typeInt = new byte[4];
int bytesRead = pbis.read(typeInt);
if (bytesRead == 4) {
int srid = geometry.getSrid();
byte[] sridInt = new byte[4];
ByteOrderValues.putInt(srid, sridInt, byteOrder==0?ByteOrderValues.BIG_ENDIAN:ByteOrderValues.LITTLE_ENDIAN);
pbis.unread(sridInt);
typeInt[byteOrder==0?0:3] |= 0x20;
}
pbis.unread(typeInt, 0, bytesRead);
pbis.unread(byteOrder);
return pbis;
}
});
return new BlobType(blobImpl);
}
}