// Copyright 2017 JanusGraph Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.janusgraph.core.attribute;
import com.google.common.base.Preconditions;
import com.spatial4j.core.context.jts.DatelineRule;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.io.jts.JtsBinaryCodec;
import com.spatial4j.core.io.jts.JtsGeoJSONReader;
import com.spatial4j.core.io.jts.JtsGeoJSONWriter;
import com.spatial4j.core.io.jts.JtsWKTReader;
import com.spatial4j.core.io.jts.JtsWKTWriter;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
/**
* Extension of default spatial context and associated I/O operations to use the Java Topology Suite (JTS), which adds
* support for line and polygon shapes.
*/
public class JtsGeoshapeHelper extends GeoshapeHelper {
public JtsGeoshapeHelper() {
JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
factory.geo = true;
factory.useJtsPoint = false;
factory.useJtsLineString = true;
// TODO: Use default dateline rule and update to support multiline/polygon to resolve wrapping issues
factory.datelineRule = DatelineRule.none;
JtsSpatialContext context = new JtsSpatialContext(factory);
super.context = context;
wktReader = new JtsWKTReader(context, factory);
wktWriter = new JtsWKTWriter(context, factory);
geojsonReader = new JtsGeoJSONReader(context, factory);
geojsonWriter = new JtsGeoJSONWriter(context, factory);
binaryCodec = new JtsBinaryCodec(context, factory);
}
public Geoshape geoshape(com.vividsolutions.jts.geom.Geometry geometry) {
return new Geoshape(((JtsSpatialContext) context).makeShape(geometry));
}
@Override
public Shape readGeometry(DataInputStream dataInput) throws IOException {
return ((JtsBinaryCodec) binaryCodec).readJtsGeom(dataInput);
}
@Override
public void write(DataOutputStream dataOutput, Geoshape attribute) throws IOException {
if (attribute.getShape() instanceof JtsGeometry) {
((JtsBinaryCodec) binaryCodec).writeJtsGeom(dataOutput, attribute.getShape());
} else {
binaryCodec.writeShape(dataOutput, attribute.getShape());
}
}
@Override
public Geoshape polygon(List<double[]> coordinates) {
Preconditions.checkArgument(coordinates.size() >= 4, "Too few coordinate pairs provided");
Coordinate[] points = new Coordinate[coordinates.size()];
for (int i=0; i<coordinates.size(); i++) {
double[] coordinate = coordinates.get(i);
Preconditions.checkArgument(coordinate.length==2 && Geoshape.isValidCoordinate(coordinate[1],coordinate[0]),"Invalid coordinate provided");
points[i] = new Coordinate(coordinate[0], coordinate[1]);
}
GeometryFactory factory = new GeometryFactory();
return new Geoshape(((JtsSpatialContext) context).makeShape(factory.createPolygon(points)));
}
@Override
public Geoshape.Type getType(Shape shape) {
final Geoshape.Type type;
if (JtsGeometry.class.isAssignableFrom(shape.getClass()) && "LineString".equals(((JtsGeometry) shape).getGeom().getGeometryType())) {
type = Geoshape.Type.LINE;
} else if (JtsGeometry.class.isAssignableFrom(shape.getClass())) {
try {
type = Geoshape.Type.fromGson((((JtsGeometry) shape).getGeom().getGeometryType()));
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Unrecognized shape type");
}
} else {
type = super.getType(shape);
}
return type;
}
@Override
public int size(Shape shape) {
switch(getType(shape)) {
case LINE:
case POLYGON:
return ((JtsGeometry) shape).getGeom().getCoordinates().length;
default:
return super.size(shape);
}
}
@Override
public Geoshape.Point getPoint(Geoshape geoshape, int position) {
Shape shape = geoshape.getShape();
if (position<0 || position>=size(shape)) throw new ArrayIndexOutOfBoundsException("Invalid position: " + position);
switch(getType(shape)) {
case LINE:
case POLYGON:
Coordinate coordinate = ((JtsGeometry) shape).getGeom().getCoordinates()[position];
return new Geoshape.Point(coordinate.y, coordinate.x);
default:
return super.getPoint(geoshape, position);
}
}
@Override
public boolean isJts(Shape shape) {
return shape instanceof JtsGeometry;
}
}