/*
* Copyright 2014-2017 the original author or 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.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.geo.Shape;
import org.springframework.data.mongodb.core.geo.GeoJson;
import org.springframework.data.mongodb.core.geo.GeoJsonGeometryCollection;
import org.springframework.data.mongodb.core.geo.GeoJsonLineString;
import org.springframework.data.mongodb.core.geo.GeoJsonMultiLineString;
import org.springframework.data.mongodb.core.geo.GeoJsonMultiPoint;
import org.springframework.data.mongodb.core.geo.GeoJsonMultiPolygon;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.data.mongodb.core.geo.Sphere;
import org.springframework.data.mongodb.core.query.GeoCommand;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;
import com.mongodb.BasicDBList;
/**
* Wrapper class to contain useful geo structure converters for the usage with Mongo.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thiago Diniz da Silveira
* @since 1.5
*/
abstract class GeoConverters {
/**
* Private constructor to prevent instantiation.
*/
private GeoConverters() {}
/**
* Returns the geo converters to be registered.
*
* @return
*/
@SuppressWarnings("unchecked")
public static Collection<? extends Object> getConvertersToRegister() {
return Arrays.asList( //
BoxToDocumentConverter.INSTANCE //
, PolygonToDocumentConverter.INSTANCE //
, CircleToDocumentConverter.INSTANCE //
, SphereToDocumentConverter.INSTANCE //
, DocumentToBoxConverter.INSTANCE //
, DocumentToPolygonConverter.INSTANCE //
, DocumentToCircleConverter.INSTANCE //
, DocumentToSphereConverter.INSTANCE //
, DocumentToPointConverter.INSTANCE //
, PointToDocumentConverter.INSTANCE //
, GeoCommandToDocumentConverter.INSTANCE //
, GeoJsonToDocumentConverter.INSTANCE //
, GeoJsonPointToDocumentConverter.INSTANCE //
, GeoJsonPolygonToDocumentConverter.INSTANCE //
, DocumentToGeoJsonPointConverter.INSTANCE //
, DocumentToGeoJsonPolygonConverter.INSTANCE //
, DocumentToGeoJsonLineStringConverter.INSTANCE //
, DocumentToGeoJsonMultiLineStringConverter.INSTANCE //
, DocumentToGeoJsonMultiPointConverter.INSTANCE //
, DocumentToGeoJsonMultiPolygonConverter.INSTANCE //
, DocumentToGeoJsonGeometryCollectionConverter.INSTANCE);
}
/**
* Converts a {@link List} of {@link Double}s into a {@link Point}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
static enum DocumentToPointConverter implements Converter<Document, Point> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Point convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(source.keySet().size() == 2, "Source must contain 2 elements");
if (source.containsKey("type")) {
return DocumentToGeoJsonPointConverter.INSTANCE.convert(source);
}
return new Point(toPrimitiveDoubleValue(source.get("x")), toPrimitiveDoubleValue(source.get("y")));
}
}
/**
* Converts a {@link Point} into a {@link List} of {@link Double}s.
*
* @author Thomas Darimont
* @since 1.5
*/
static enum PointToDocumentConverter implements Converter<Point, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(Point source) {
return source == null ? null : new Document("x", source.getX()).append("y", source.getY());
}
}
/**
* Converts a {@link Box} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
@WritingConverter
static enum BoxToDocumentConverter implements Converter<Box, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(Box source) {
if (source == null) {
return null;
}
Document result = new Document();
result.put("first", PointToDocumentConverter.INSTANCE.convert(source.getFirst()));
result.put("second", PointToDocumentConverter.INSTANCE.convert(source.getSecond()));
return result;
}
}
/**
* Converts a {@link BasicDBList} into a {@link Box}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
static enum DocumentToBoxConverter implements Converter<Document, Box> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Box convert(Document source) {
if (source == null) {
return null;
}
Point first = DocumentToPointConverter.INSTANCE.convert((Document) source.get("first"));
Point second = DocumentToPointConverter.INSTANCE.convert((Document) source.get("second"));
return new Box(first, second);
}
}
/**
* Converts a {@link Circle} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
static enum CircleToDocumentConverter implements Converter<Circle, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(Circle source) {
if (source == null) {
return null;
}
Document result = new Document();
result.put("center", PointToDocumentConverter.INSTANCE.convert(source.getCenter()));
result.put("radius", source.getRadius().getNormalizedValue());
result.put("metric", source.getRadius().getMetric().toString());
return result;
}
}
/**
* Converts a {@link Document} into a {@link Circle}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
static enum DocumentToCircleConverter implements Converter<Document, Circle> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Circle convert(Document source) {
if (source == null) {
return null;
}
Document center = (Document) source.get("center");
Number radius = (Number) source.get("radius");
Assert.notNull(center, "Center must not be null!");
Assert.notNull(radius, "Radius must not be null!");
Distance distance = new Distance(toPrimitiveDoubleValue(radius));
if (source.containsKey("metric")) {
String metricString = (String) source.get("metric");
Assert.notNull(metricString, "Metric must not be null!");
distance = distance.in(Metrics.valueOf(metricString));
}
return new Circle(DocumentToPointConverter.INSTANCE.convert(center), distance);
}
}
/**
* Converts a {@link Sphere} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
static enum SphereToDocumentConverter implements Converter<Sphere, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(Sphere source) {
if (source == null) {
return null;
}
Document result = new Document();
result.put("center", PointToDocumentConverter.INSTANCE.convert(source.getCenter()));
result.put("radius", source.getRadius().getNormalizedValue());
result.put("metric", source.getRadius().getMetric().toString());
return result;
}
}
/**
* Converts a {@link BasicDBList} into a {@link Sphere}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
static enum DocumentToSphereConverter implements Converter<Document, Sphere> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Sphere convert(Document source) {
if (source == null) {
return null;
}
Document center = (Document) source.get("center");
Number radius = (Number) source.get("radius");
Assert.notNull(center, "Center must not be null!");
Assert.notNull(radius, "Radius must not be null!");
Distance distance = new Distance(toPrimitiveDoubleValue(radius));
if (source.containsKey("metric")) {
String metricString = (String) source.get("metric");
Assert.notNull(metricString, "Metric must not be null!");
distance = distance.in(Metrics.valueOf(metricString));
}
return new Sphere(DocumentToPointConverter.INSTANCE.convert(center), distance);
}
}
/**
* Converts a {@link Polygon} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
static enum PolygonToDocumentConverter implements Converter<Polygon, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(Polygon source) {
if (source == null) {
return null;
}
List<Point> points = source.getPoints();
List<Document> pointTuples = new ArrayList<Document>(points.size());
for (Point point : points) {
pointTuples.add(PointToDocumentConverter.INSTANCE.convert(point));
}
Document result = new Document();
result.put("points", pointTuples);
return result;
}
}
/**
* Converts a {@link BasicDBList} into a {@link Polygon}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
static enum DocumentToPolygonConverter implements Converter<Document, Polygon> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
@SuppressWarnings({ "unchecked" })
public Polygon convert(Document source) {
if (source == null) {
return null;
}
List<Document> points = (List<Document>) source.get("points");
List<Point> newPoints = new ArrayList<Point>(points.size());
for (Document element : points) {
Assert.notNull(element, "Point elements of polygon must not be null!");
newPoints.add(DocumentToPointConverter.INSTANCE.convert(element));
}
return new Polygon(newPoints);
}
}
/**
* Converts a {@link Sphere} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
static enum GeoCommandToDocumentConverter implements Converter<GeoCommand, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
@SuppressWarnings("rawtypes")
public Document convert(GeoCommand source) {
if (source == null) {
return null;
}
List argument = new ArrayList();
Shape shape = source.getShape();
if (shape instanceof GeoJson) {
return GeoJsonToDocumentConverter.INSTANCE.convert((GeoJson) shape);
}
if (shape instanceof Box) {
argument.add(toList(((Box) shape).getFirst()));
argument.add(toList(((Box) shape).getSecond()));
} else if (shape instanceof Circle) {
argument.add(toList(((Circle) shape).getCenter()));
argument.add(((Circle) shape).getRadius().getNormalizedValue());
} else if (shape instanceof Circle) {
argument.add(toList(((Circle) shape).getCenter()));
argument.add(((Circle) shape).getRadius());
} else if (shape instanceof Polygon) {
for (Point point : ((Polygon) shape).getPoints()) {
argument.add(toList(point));
}
} else if (shape instanceof Sphere) {
argument.add(toList(((Sphere) shape).getCenter()));
argument.add(((Sphere) shape).getRadius().getNormalizedValue());
}
return new Document(source.getCommand(), argument);
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
@SuppressWarnings("rawtypes")
static enum GeoJsonToDocumentConverter implements Converter<GeoJson, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(GeoJson source) {
if (source == null) {
return null;
}
Document dbo = new Document("type", source.getType());
if (source instanceof GeoJsonGeometryCollection) {
List dbl = new ArrayList();
for (GeoJson geometry : ((GeoJsonGeometryCollection) source).getCoordinates()) {
dbl.add(convert(geometry));
}
dbo.put("geometries", dbl);
} else {
dbo.put("coordinates", convertIfNecessarry(source.getCoordinates()));
}
return dbo;
}
private Object convertIfNecessarry(Object candidate) {
if (candidate instanceof GeoJson) {
return convertIfNecessarry(((GeoJson) candidate).getCoordinates());
}
if (candidate instanceof Iterable) {
List dbl = new ArrayList();
for (Object element : (Iterable) candidate) {
dbl.add(convertIfNecessarry(element));
}
return dbl;
}
if (candidate instanceof Point) {
return toList((Point) candidate);
}
return candidate;
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum GeoJsonPointToDocumentConverter implements Converter<GeoJsonPoint, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(GeoJsonPoint source) {
return GeoJsonToDocumentConverter.INSTANCE.convert(source);
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum GeoJsonPolygonToDocumentConverter implements Converter<GeoJsonPolygon, Document> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Document convert(GeoJsonPolygon source) {
return GeoJsonToDocumentConverter.INSTANCE.convert(source);
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum DocumentToGeoJsonPointConverter implements Converter<Document, GeoJsonPoint> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
@SuppressWarnings("unchecked")
public GeoJsonPoint convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "Point"),
String.format("Cannot convert type '%s' to Point.", source.get("type")));
List<Number> dbl = (List<Number>) source.get("coordinates");
return new GeoJsonPoint(toPrimitiveDoubleValue(dbl.get(0)), toPrimitiveDoubleValue(dbl.get(1)));
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum DocumentToGeoJsonPolygonConverter implements Converter<Document, GeoJsonPolygon> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public GeoJsonPolygon convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "Polygon"),
String.format("Cannot convert type '%s' to Polygon.", source.get("type")));
return toGeoJsonPolygon((List) source.get("coordinates"));
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum DocumentToGeoJsonMultiPolygonConverter implements Converter<Document, GeoJsonMultiPolygon> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public GeoJsonMultiPolygon convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "MultiPolygon"),
String.format("Cannot convert type '%s' to MultiPolygon.", source.get("type")));
List dbl = (List) source.get("coordinates");
List<GeoJsonPolygon> polygones = new ArrayList<GeoJsonPolygon>();
for (Object polygon : dbl) {
polygones.add(toGeoJsonPolygon((List) polygon));
}
return new GeoJsonMultiPolygon(polygones);
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum DocumentToGeoJsonLineStringConverter implements Converter<Document, GeoJsonLineString> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public GeoJsonLineString convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "LineString"),
String.format("Cannot convert type '%s' to LineString.", source.get("type")));
List cords = (List) source.get("coordinates");
return new GeoJsonLineString(toListOfPoint(cords));
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum DocumentToGeoJsonMultiPointConverter implements Converter<Document, GeoJsonMultiPoint> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public GeoJsonMultiPoint convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "MultiPoint"),
String.format("Cannot convert type '%s' to MultiPoint.", source.get("type")));
List cords = (List) source.get("coordinates");
return new GeoJsonMultiPoint(toListOfPoint(cords));
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum DocumentToGeoJsonMultiLineStringConverter implements Converter<Document, GeoJsonMultiLineString> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public GeoJsonMultiLineString convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "MultiLineString"),
String.format("Cannot convert type '%s' to MultiLineString.", source.get("type")));
List<GeoJsonLineString> lines = new ArrayList<GeoJsonLineString>();
List cords = (List) source.get("coordinates");
for (Object line : cords) {
lines.add(new GeoJsonLineString(toListOfPoint((List) line)));
}
return new GeoJsonMultiLineString(lines);
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static enum DocumentToGeoJsonGeometryCollectionConverter implements Converter<Document, GeoJsonGeometryCollection> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@SuppressWarnings("rawtypes")
@Override
public GeoJsonGeometryCollection convert(Document source) {
if (source == null) {
return null;
}
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "GeometryCollection"),
String.format("Cannot convert type '%s' to GeometryCollection.", source.get("type")));
List<GeoJson<?>> geometries = new ArrayList<GeoJson<?>>();
for (Object o : (List) source.get("geometries")) {
geometries.add(convertGeometries((Document) o));
}
return new GeoJsonGeometryCollection(geometries);
}
private static GeoJson<?> convertGeometries(Document source) {
Object type = source.get("type");
if (ObjectUtils.nullSafeEquals(type, "Point")) {
return DocumentToGeoJsonPointConverter.INSTANCE.convert(source);
}
if (ObjectUtils.nullSafeEquals(type, "MultiPoint")) {
return DocumentToGeoJsonMultiPointConverter.INSTANCE.convert(source);
}
if (ObjectUtils.nullSafeEquals(type, "LineString")) {
return DocumentToGeoJsonLineStringConverter.INSTANCE.convert(source);
}
if (ObjectUtils.nullSafeEquals(type, "MultiLineString")) {
return DocumentToGeoJsonMultiLineStringConverter.INSTANCE.convert(source);
}
if (ObjectUtils.nullSafeEquals(type, "Polygon")) {
return DocumentToGeoJsonPolygonConverter.INSTANCE.convert(source);
}
if (ObjectUtils.nullSafeEquals(type, "MultiPolygon")) {
return DocumentToGeoJsonMultiPolygonConverter.INSTANCE.convert(source);
}
throw new IllegalArgumentException(String.format("Cannot convert unknown GeoJson type %s", type));
}
}
static List<Double> toList(Point point) {
return Arrays.asList(point.getX(), point.getY());
}
/**
* Converts a coordinate pairs nested in in {@link BasicDBList} into {@link GeoJsonPoint}s.
*
* @param listOfCoordinatePairs
* @return
* @since 1.7
*/
@SuppressWarnings("unchecked")
static List<Point> toListOfPoint(List listOfCoordinatePairs) {
List<Point> points = new ArrayList<Point>();
for (Object point : listOfCoordinatePairs) {
Assert.isInstanceOf(List.class, point);
List<Number> coordinatesList = (List<Number>) point;
points.add(new GeoJsonPoint(toPrimitiveDoubleValue(coordinatesList.get(0)),
toPrimitiveDoubleValue(coordinatesList.get(1))));
}
return points;
}
/**
* Converts a coordinate pairs nested in in {@link BasicDBList} into {@link GeoJsonPolygon}.
*
* @param dbList
* @return
* @since 1.7
*/
static GeoJsonPolygon toGeoJsonPolygon(List dbList) {
return new GeoJsonPolygon(toListOfPoint((List) dbList.get(0)));
}
private static double toPrimitiveDoubleValue(Object value) {
Assert.isInstanceOf(Number.class, value, "Argument must be a Number.");
return NumberUtils.convertNumberToTargetClass((Number) value, Double.class).doubleValue();
}
}