// Copyright © 2015 HSL <https://www.hsl.fi> // This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses. package fi.hsl.parkandride.core.domain; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.Double.parseDouble; import static org.geolatte.geom.DimensionalFlag.d2D; import static org.geolatte.geom.PointSequenceBuilders.variableSized; import java.util.ArrayList; import java.util.List; import org.antlr.v4.runtime.*; import org.geolatte.geom.*; import org.geolatte.geom.codec.Wkt; import org.geolatte.geom.crs.CrsId; import com.google.common.collect.Lists; import fi.hsl.parkandride.core.domain.wkt.WKTBaseVisitor; import fi.hsl.parkandride.core.domain.wkt.WKTLexer; import fi.hsl.parkandride.core.domain.wkt.WKTParser; public class Spatial { public static final CrsId WGS84 = CrsId.valueOf(4326); private static final ANTLRErrorListener ERROR_LISTENER = new BaseErrorListener() { @Override public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { throw new IllegalArgumentException("Line " + line + ":" + charPositionInLine + " " + msg); } }; public static Geometry fromWkt(String wkt) { return parseWKT(wkt); } public static String toWkt(Geometry geometry) { return Wkt.newEncoder(Wkt.Dialect.POSTGIS_EWKT_1).encode(geometry); } public static Polygon fromWktPolygon(String wkt) { return (Polygon) parseWKT(wkt); } public static Geometry parseWKT(String wkt) { if (isNullOrEmpty(wkt)) { return null; } try { return newParser(wkt).geometry().accept(WKT_VISITOR).toGeometry(); } catch (RuntimeException e) { throw new IllegalArgumentException( "Expected a valid WKT Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon. " + e.getMessage(), e); } } private static WKTParser newParser(String input) { WKTLexer lexer = new WKTLexer(new ANTLRInputStream(input)); lexer.removeErrorListeners(); lexer.addErrorListener(ERROR_LISTENER); WKTParser parser = new WKTParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); parser.addErrorListener(ERROR_LISTENER); return parser; } private static final WKTBaseVisitor<Builder> WKT_VISITOR = new WKTBaseVisitor<Builder>() { @Override public Builder visitPointGeometry(WKTParser.PointGeometryContext ctx) { return new GeometryWrapper(((Points) visitChildren(ctx)).toPoint()); } @Override public Builder visitLineStringGeometry(WKTParser.LineStringGeometryContext ctx) { return new GeometryWrapper(((Lines) visitChildren(ctx)).toLineString()); } @Override public Builder visitPolygonGeometry(WKTParser.PolygonGeometryContext ctx) { return new GeometryWrapper(((Shapes) visitChildren(ctx)).toPolygon()); } @Override public Builder visitMultiPointGeometry(WKTParser.MultiPointGeometryContext ctx) { return new GeometryWrapper(((Points) visitChildren(ctx)).toMultiPoint()); } @Override public Builder visitMultiLineStringGeometry(WKTParser.MultiLineStringGeometryContext ctx) { return new GeometryWrapper(((Lines) visitChildren(ctx)).toMultiLineString()); } @Override public Builder visitMultiPolygonGeometry(WKTParser.MultiPolygonGeometryContext ctx) { return new GeometryWrapper(((Shapes) visitChildren(ctx)).toMultiPolygon()); } @Override public Builder visitPolygon(WKTParser.PolygonContext ctx) { Lines lines = (Lines) visitChildren(ctx); return new Shapes(lines); } @Override public Builder visitLineString(WKTParser.LineStringContext ctx) { Points points = (Points) visitChildren(ctx); return new Lines(points); } @Override public Builder visitPoint(WKTParser.PointContext ctx) { return new Points(parseDouble(ctx.x.getText()), parseDouble(ctx.y.getText())); } @Override protected Builder aggregateResult(Builder aggregate, Builder nextResult) { return aggregate != null ? aggregate.append(nextResult) : nextResult; } }; private abstract static class Builder { Builder append(Builder builder) { throw new UnsupportedOperationException(); } Geometry toGeometry() { throw new UnsupportedOperationException(); } } private static class GeometryWrapper extends Builder { private final Geometry geometry; private GeometryWrapper(Geometry geometry) { this.geometry = geometry; } @Override Geometry toGeometry() { return geometry; } } private static class Points extends Builder { final PointSequenceBuilder points = variableSized(d2D, WGS84); public Points(double x, double y) { points.add(x, y); } @Override Builder append(Builder builder) { if (builder != null) { Points other = (Points) builder; PointSequence pointSequence = other.points.toPointSequence(); for (int i = 0; i < pointSequence.size(); i++) { points.add(pointSequence.getX(i), pointSequence.getY(i)); } } return this; } Point toPoint() { return new Point(points.toPointSequence()); } LineString toLineString() { return new LineString(points.toPointSequence()); } LinearRing toLinearRing() { return new LinearRing(points.toPointSequence()); } MultiPoint toMultiPoint() { return new MultiPoint(toPointArray()); } Point[] toPointArray() { PointSequence pointSequence = points.toPointSequence(); Point[] pointArray = new Point[pointSequence.size()]; int i=0; for (Point point : pointSequence) { pointArray[i++] = point; } return pointArray; } } private static class Lines extends Builder { private final List<Points> lines = new ArrayList<>(); Lines(Points points) { lines.add(points); } @Override Builder append(Builder builder) { if (builder != null) { lines.addAll(((Lines) builder).lines); } return this; } Polygon toPolygon() { return new Polygon(toLinearRings()); } LinearRing[] toLinearRings() { List<LinearRing> rings = Lists.transform(lines, Points::toLinearRing); return rings.toArray(rings.toArray(new LinearRing[rings.size()])); } Geometry toMultiLineString() { return new MultiLineString(toLineStrings()); } LineString[] toLineStrings() { List<LineString> lineStrings = Lists.transform(lines, Points::toLineString); return lineStrings.toArray(new LineString[lineStrings.size()]); } LineString toLineString() { return lines.get(0).toLineString(); } } private static class Shapes extends Builder { private final List<Lines> shapes = new ArrayList<>(); Shapes(Lines lines) { shapes.add(lines); } @Override Builder append(Builder builder) { if (builder != null) { shapes.addAll(((Shapes) builder).shapes); } return this; } Polygon toPolygon() { return shapes.get(0).toPolygon(); } Geometry toMultiPolygon() { return new MultiPolygon(toPolygons()); } Polygon[] toPolygons() { List<Polygon> polygons = Lists.transform(shapes, Lines::toPolygon); return polygons.toArray(new Polygon[polygons.size()]); } } }