Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for MongoDB GeoJSON Points #2008

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -67,7 +67,7 @@
<animal-sniffer.version>1.14</animal-sniffer.version>

<jdo.version>3.0.1</jdo.version>
<morphia.version>0.105</morphia.version>
<morphia.version>0.111</morphia.version>

<!-- Import-Package definitions for maven-bundle-plugin -->
<osgi.import.package.root>
Expand Down
4 changes: 2 additions & 2 deletions querydsl-mongodb/pom.xml
Expand Up @@ -16,10 +16,10 @@
<packaging>jar</packaging>

<properties>
<mongodb.version>2.10.0</mongodb.version>
<mongodb.version>2.13.0</mongodb.version>
<osgi.import.package>
com.mongodb;version="0.0.0",
org.mongodb.morphia.*;version="0.105",
org.mongodb.morphia.*;version="0.111",
org.bson.*;version="0.0.0",
${osgi.import.package.root}
</osgi.import.package>
Expand Down
Expand Up @@ -51,4 +51,8 @@ public BooleanExpression near(double latVal, double longVal) {
return MongodbExpressions.near(this, latVal, longVal);
}

public BooleanExpression nearSphere(double latVal, double longVal) {
return MongodbExpressions.nearSphere(this, latVal, longVal);
}

}
@@ -0,0 +1,55 @@
/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* 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 com.querydsl.mongodb.morphia;

import org.mongodb.morphia.geo.Point;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.mongodb.MongodbOps;

/**
* Morphia specific MongoDB operations
*
* @author jmoghisi
*
*/
public final class MorphiaExpressions {

private MorphiaExpressions() { }

/**
* Finds the closest points relative to the given location and orders the results with decreasing proximity
*
* @param expr expression
* @param point location
* @return predicate
*/
public static BooleanExpression near(Expression<Point> expr, Point point) {
return Expressions.booleanOperation(MongodbOps.NEAR, expr, Expressions.constant(point));
}

/**
* Finds the closest points relative to the given location on a sphere and orders the results with decreasing proximity
*
* @param expr expression
* @param point location
* @return predicate
*/
public static BooleanExpression nearSphere(Expression<Point> expr, Point point) {
return Expressions.booleanOperation(MongodbOps.NEAR_SPHERE, expr, Expressions.constant(point));
}

}
Expand Up @@ -20,9 +20,13 @@
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Property;
import org.mongodb.morphia.annotations.Reference;
import org.mongodb.morphia.geo.Geometry;
import org.mongodb.morphia.geo.GeometryQueryConverter;
import org.mongodb.morphia.mapping.Mapper;

import com.google.common.base.Preconditions;
import com.mongodb.DBRef;
import com.querydsl.core.types.Constant;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.mongodb.MongodbSerializer;
Expand All @@ -36,9 +40,21 @@
public class MorphiaSerializer extends MongodbSerializer {

private final Morphia morphia;
private final GeometryQueryConverter geometryQueryConverter;

public MorphiaSerializer(Morphia morphia) {
Preconditions.checkNotNull(morphia);
this.morphia = morphia;
this.geometryQueryConverter = new GeometryQueryConverter(morphia.getMapper());
}

@Override
public Object visit(Constant<?> expr, Void context) {
if (Geometry.class.isAssignableFrom(expr.getType())) {
return geometryQueryConverter.encode(expr.getConstant());
} else {
return super.visit(expr, context);
}
}

@Override
Expand Down
63 changes: 63 additions & 0 deletions querydsl-mongodb/src/main/java/org/mongodb/morphia/geo/QPoint.java
@@ -0,0 +1,63 @@
package org.mongodb.morphia.geo;

import javax.annotation.Nullable;
import java.lang.reflect.AnnotatedElement;

import com.querydsl.core.types.*;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.SimpleExpression;
import com.querydsl.mongodb.morphia.MorphiaExpressions;

/**
* {@code QPoint} represents Point paths, needed to support geo spatial querying features of Mongodb
*
* @author jmoghisi
*
*/
public class QPoint extends SimpleExpression<Point> implements Path<Point> {

private final PathImpl<Point> pathMixin;

public QPoint(String var) {
this(PathMetadataFactory.forVariable(var));
}

public QPoint(Path<?> parent, String property) {
this(PathMetadataFactory.forProperty(parent, property));
}

public QPoint(PathMetadata metadata) {
super(ExpressionUtils.path(Point.class, metadata));
this.pathMixin = (PathImpl<Point>) mixin;
}

@Override
public PathMetadata getMetadata() {
return pathMixin.getMetadata();
}

@Override
public Path<?> getRoot() {
return pathMixin.getRoot();
}

@Override
public AnnotatedElement getAnnotatedElement() {
return pathMixin.getAnnotatedElement();
}

@Nullable
@Override
public <R, C> R accept(Visitor<R, C> v, @Nullable C context) {
return v.visit(pathMixin, context);
}

public BooleanExpression near(Point point) {
return MorphiaExpressions.near(this, point);
}

public BooleanExpression nearSphere(Point point) {
return MorphiaExpressions.nearSphere(this, point);
}

}
@@ -0,0 +1,18 @@
/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* 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.
*/

/**
* MongoDB geo support
*/
package org.mongodb.morphia.geo;
Expand Up @@ -23,9 +23,11 @@
import org.junit.experimental.categories.Category;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.geo.GeoJson;
import org.mongodb.morphia.utils.IndexType;

import com.mongodb.BasicDBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.querydsl.core.testutil.MongoDB;
import com.querydsl.mongodb.domain.GeoEntity;
Expand All @@ -36,42 +38,57 @@
public class GeoSpatialQueryTest {

private final String dbname = "geodb";
private final Mongo mongo;
private final MongoClient mongo;
private final Morphia morphia;
private final Datastore ds;
private final QGeoEntity geoEntity = new QGeoEntity("geoEntity");

public GeoSpatialQueryTest() throws UnknownHostException, MongoException {
mongo = new Mongo();
mongo = new MongoClient();
morphia = new Morphia().map(GeoEntity.class);
ds = morphia.createDatastore(mongo, dbname);
}

@Before
public void before() {
ds.delete(ds.createQuery(GeoEntity.class));
ds.getCollection(GeoEntity.class).ensureIndex(new BasicDBObject("location","2d"));
}

@Test
public void near() {
String locationFieldName = QGeoEntity.geoEntity.location.getMetadata().getName();
ds.getCollection(GeoEntity.class).createIndex(new BasicDBObject(locationFieldName, IndexType.GEO2D.toIndexValue()));

String geoJsonLocationFieldName = QGeoEntity.geoEntity.locationAsGeoJson().getMetadata().getName();
ds.getCollection(GeoEntity.class).createIndex(new BasicDBObject(geoJsonLocationFieldName, IndexType.GEO2DSPHERE.toIndexValue()));

ds.save(new GeoEntity(10.0, 50.0));
ds.save(new GeoEntity(20.0, 50.0));
ds.save(new GeoEntity(30.0, 50.0));
}

@Test
public void near() {
List<GeoEntity> entities = query().where(geoEntity.location.near(50.0, 50.0)).fetch();
assertEquals(30.0, entities.get(0).getLocation()[0], 0.1);
assertEquals(20.0, entities.get(1).getLocation()[0], 0.1);
assertEquals(10.0, entities.get(2).getLocation()[0], 0.1);
assertEntities(entities);
}

@Test
public void geojson_near() {
List<GeoEntity> entities = query().where(geoEntity.locationAsGeoJson().near(GeoJson.point(50.0, 50.0))).fetch();
assertEntities(entities);
}

@Test
public void near_sphere() {
ds.save(new GeoEntity(10.0, 50.0));
ds.save(new GeoEntity(20.0, 50.0));
ds.save(new GeoEntity(30.0, 50.0));
List<GeoEntity> entities = query().where(geoEntity.location.nearSphere(50.0, 50.0)).fetch();
assertEntities(entities);
}

@Test
public void geojson_near_sphere() {
List<GeoEntity> entities = query().where(geoEntity.locationAsGeoJson().nearSphere(GeoJson.point(50.0, 50.0))).fetch();
assertEntities(entities);
}

List<GeoEntity> entities = query().where(MongodbExpressions.nearSphere(geoEntity.location, 50.0, 50.0)).fetch();
private void assertEntities(List<GeoEntity> entities) {
assertEquals(30.0, entities.get(0).getLocation()[0], 0.1);
assertEquals(20.0, entities.get(1).getLocation()[0], 0.1);
assertEquals(10.0, entities.get(2).getLocation()[0], 0.1);
Expand Down
Expand Up @@ -10,7 +10,7 @@
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;

import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.querydsl.core.testutil.MongoDB;
import com.querydsl.core.types.Predicate;
Expand All @@ -22,7 +22,7 @@
@Category(MongoDB.class)
public class JoinTest {

private final Mongo mongo;
private final MongoClient mongo;
private final Morphia morphia;
private final Datastore ds;

Expand All @@ -33,7 +33,7 @@ public class JoinTest {
private final QUser enemy = new QUser("enemy");

public JoinTest() throws UnknownHostException, MongoException {
mongo = new Mongo();
mongo = new MongoClient();
morphia = new Morphia().map(User.class).map(Item.class);
ds = morphia.createDatastore(mongo, dbname);
}
Expand Down
Expand Up @@ -28,7 +28,7 @@

import com.google.common.collect.Lists;
import com.mongodb.BasicDBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
import com.querydsl.core.NonUniqueResultException;
Expand All @@ -46,7 +46,7 @@
@Category(MongoDB.class)
public class MongodbQueryTest {

private final Mongo mongo;
private final MongoClient mongo;
private final Morphia morphia;
private final Datastore ds;

Expand All @@ -62,7 +62,7 @@ public class MongodbQueryTest {
City tampere, helsinki;

public MongodbQueryTest() throws UnknownHostException, MongoException {
mongo = new Mongo();
mongo = new MongoClient();
morphia = new Morphia().map(User.class).map(Item.class).map(MapEntity.class).map(Dates.class);
ds = morphia.createDatastore(mongo, dbname);
}
Expand Down
Expand Up @@ -23,6 +23,9 @@
import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Test;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.geo.GeoJson;
import org.mongodb.morphia.geo.QPoint;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
Expand All @@ -34,6 +37,7 @@
import com.querydsl.mongodb.domain.QDummyEntity;
import com.querydsl.mongodb.domain.QPerson;
import com.querydsl.mongodb.domain.QUser;
import com.querydsl.mongodb.morphia.MorphiaExpressions;
import com.querydsl.mongodb.morphia.MorphiaSerializer;

public class MongodbSerializerTest {
Expand All @@ -57,7 +61,7 @@ public class MongodbSerializerTest {

@Before
public void before() {
serializer = new MorphiaSerializer(null);
serializer = new MorphiaSerializer(new Morphia());
entityPath = new PathBuilder<Object>(Object.class, "obj");
title = entityPath.getString("title");
year = entityPath.getNumber("year", Integer.class);
Expand Down Expand Up @@ -241,12 +245,26 @@ public void near() {
dbo("point", dbo("$near", dblist(1.0, 2.0))));
}

@Test
public void near_geojson() {
DBObject geoJsonPoint = dbo("$geometry", dbo("type", "Point").append("coordinates", dblist(1.0, 2.0)));
BooleanExpression actualExpr = MorphiaExpressions.near(new QPoint("point"), GeoJson.point(2.0, 1.0));
assertQuery(actualExpr, dbo("point", dbo("$near", geoJsonPoint)));
}

@Test
public void near_sphere() {
assertQuery(MongodbExpressions.nearSphere(new Point("point"), 1.0, 2.0),
dbo("point", dbo("$nearSphere", dblist(1.0, 2.0))));
}

@Test
public void near_sphere_geojson() {
DBObject geoJsonPoint = dbo("$geometry", dbo("type", "Point").append("coordinates", dblist(1.0, 2.0)));
BooleanExpression actualExpr = MorphiaExpressions.nearSphere(new QPoint("point"), GeoJson.point(2.0, 1.0));
assertQuery(actualExpr, dbo("point", dbo("$nearSphere", geoJsonPoint)));
}

@Test
public void not() {
assertQuery(title.eq("A").not(), dbo("title", dbo("$ne","A")));
Expand Down