Skip to content

Commit

Permalink
This closes apache#430 and adds geo-distance to queries with a within…
Browse files Browse the repository at this point in the history
… clause
  • Loading branch information
snoopdave committed Nov 10, 2015
2 parents c3299bc + 1c0b0a9 commit 2b1f761
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.apache.usergrid.corepersistence.index.IndexLocationStrategyFactory;
import org.apache.usergrid.persistence.index.*;
import org.apache.usergrid.persistence.index.impl.IndexProducer;
import org.apache.usergrid.persistence.model.field.DistanceField;
import org.apache.usergrid.persistence.model.field.DoubleField;
import org.apache.usergrid.persistence.model.field.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -197,6 +199,7 @@ private void validate( final FilterResult<Candidate> filterResult ) {

final Candidate candidate = filterResult.getValue();
final CandidateResult candidateResult = candidate.getCandidateResult();
final boolean isGeo = candidateResult instanceof GeoCandidateResult;
final SearchEdge searchEdge = candidate.getSearchEdge();
final Id candidateId = candidateResult.getId();
final UUID candidateVersion = candidateResult.getVersion();
Expand Down Expand Up @@ -252,6 +255,9 @@ private void validate( final FilterResult<Candidate> filterResult ) {
//they're the same add it

final Entity returnEntity = entity.getEntity().get();
if(isGeo){
returnEntity.setField(new DistanceField(((GeoCandidateResult)candidateResult).getDistance()));
}

final Optional<EdgePath> parent = filterResult.getPath();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,49 @@ public void testMovingTarget() throws Exception {
em.delete(user);
}


/**
* Validate the ability to query a moving entity
* 1. Create an entity with location
* 2. Query from a point near the entity's location
* 3. Move the entity farther away from the center point
* 4. Run the same query again to verify the entity is no longer in the area
*/
@Test
public void validateDistanceQueryExists() throws Exception {
LOG.info("GeoIT.validateDistanceQueryExists");
//Get the EntityManager instance
EntityManager em = app.getEntityManager();
assertNotNull(em);

//1. Create an entity with location
Map<String, Object> properties = new LinkedHashMap<String, Object>() {{
put("username", "edanuff");
put("email", "[email protected]");
put("location", new LinkedHashMap<String, Object>() {{
put("latitude", 37.776753);
put("longitude", -122.407846);
}});
}};
Entity user = em.create("user", properties);
assertNotNull(user);
app.refreshIndex();

final double lat = 37.776753;
final double lon = -122.407846;

//2. Query from a point near the entity's location
Query query = Query.fromQL("select * where location within 100 of "
+ lat + "," + lon);
Results listResults = em.searchCollection(em.getApplicationRef(), "users", query);
assertEquals(1, listResults.size());
Entity entity = listResults.getEntity();
assertTrue(entity.getMetadata("distance")!=null);
assertTrue(Double.parseDouble( entity.getMetadata("distance").toString())>0);

em.delete(user);
}

/**
* Validate the ability to query connections within proximity of the users
* 1. Create an entity with location
Expand Down Expand Up @@ -461,11 +504,20 @@ public void testPointPaging() throws Exception {
int count = 0;
Results results;

double previousDistance = 0d;
do {
results = em.searchCollection(em.getApplicationRef(), "stores", query);

for (Entity entity : results.getEntities()) {
assertEquals(String.valueOf(count), entity.getName());

Object distanceObject = entity.getMetadata("distance");
assertNotNull( distanceObject );
assertTrue( distanceObject instanceof Double );
double distance = (Double)distanceObject;
assertTrue( distance >= previousDistance );
previousDistance = distance;

count++;
}

Expand Down Expand Up @@ -508,13 +560,22 @@ public void testSamePointPaging() throws Exception {
int count = 0;
Results results;


double previousDistance = 0d;
do {
results = em.searchCollection(em.getApplicationRef(), "stores", query);

for (Entity entity : results.getEntities()) {
//TODO:can we assert order
final int expected = numEntities - count - 1;

Object distanceObject = entity.getMetadata("distance");
assertNotNull( distanceObject );
assertTrue( distanceObject instanceof Double );
double distance = (Double)distanceObject;
assertTrue( distance >= previousDistance );
previousDistance = distance;

assertEquals(String.valueOf(expected), entity.getName());
count++;
}
Expand Down Expand Up @@ -574,8 +635,18 @@ public void testDistanceByLimit() throws Exception {
do {
Results results = em.searchCollection(em.getApplicationRef(), "stores", query);

double previousDistance = 0d;

for (Entity entity : results.getEntities()) {
assertEquals(String.valueOf(count), entity.getName());

Object distanceObject = entity.getMetadata("distance");
assertNotNull( distanceObject );
assertTrue( distanceObject instanceof Double );
double distance = (Double)distanceObject;
assertTrue( distance >= previousDistance );
previousDistance = distance;

count++;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,15 @@ public EntityMap toMap( EntityObject entityObject ) {
}

private EntityMap toMap( EntityObject entity, EntityMap entityMap ) {

for ( Field field : entity.getFields() ) {
if ( field instanceof ListField || field instanceof ArrayField || field instanceof SetField) {
if( field instanceof DistanceField){
//parse distance and add to metadata
if(!entityMap.containsKey("metadata"))entityMap.put("metadata",new HashMap<String,Object>());
DistanceField distanceField = (DistanceField) field;
Map<String,Object> metaMap = (Map) entityMap.get("metadata");
metaMap.put(DistanceField.NAME, distanceField.getValue());
}else if ( field instanceof ListField || field instanceof ArrayField || field instanceof SetField) {
Collection list = ( Collection ) field.getValue();
entityMap.put( field.getName(), processCollection( list ) );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
*
* * Licensed to the Apache Software Foundation (ASF) under one or more
* * contributor license agreements. The ASF licenses this file to You
* * 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. For additional information regarding
* * copyright in this work, please see the NOTICE file in the top level
* * directory of this distribution.
*
*/
package org.apache.usergrid.persistence.model.field;

/**
* Distance field to pass distance down the parsing chain.
*/
public class DistanceField extends DoubleField {
public static final String NAME = "distance";
public DistanceField( Double value) {
super(NAME, value);
}

public DistanceField( Double value, boolean unique) {
super(NAME, value, unique);
}

public DistanceField() {
super();
}
@Override
public FieldTypeName getTypeName() {
return FieldTypeName.DISTANCE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ public enum FieldTypeName {
LONG,
SET,
STRING,
UUID
UUID,
DISTANCE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
*
* * Licensed to the Apache Software Foundation (ASF) under one or more
* * contributor license agreements. The ASF licenses this file to You
* * 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. For additional information regarding
* * copyright in this work, please see the NOTICE file in the top level
* * directory of this distribution.
*
*/
package org.apache.usergrid.persistence.index;

import org.apache.usergrid.persistence.model.entity.Id;

import java.util.UUID;

/**
* Result for Geo candidates
*/
public class GeoCandidateResult extends CandidateResult {
private final double distance;

public GeoCandidateResult(Id entityId, UUID entityVersion, String docId, double distance) {
super(entityId, entityVersion, docId);
this.distance = distance;
}

public double getDistance() {
return distance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -669,10 +669,12 @@ private CandidateResults parseResults( final SearchResponse searchResponse, fina

List<CandidateResult> candidates = new ArrayList<>( hits.length );

for ( SearchHit hit : hits ) {

final CandidateResult candidateResult = parseIndexDocId( hit.getId() );

for ( SearchHit hit : hits ) {
CandidateResult candidateResult;

candidateResult = parseIndexDocId( hit, query.isGeoQuery() );
candidates.add( candidateResult );
}

Expand All @@ -696,7 +698,7 @@ private List<CandidateResult> aggregateScrollResults(List<CandidateResult> candi

for ( SearchHit hit : hits ) {

final CandidateResult candidateResult = parseIndexDocId( hit.getId() );
final CandidateResult candidateResult = parseIndexDocId( hit );

// if comparing against the latestVersion, make sure we only add the candidateResult if it's
// older than or equal to the latest marked version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@

import org.apache.usergrid.persistence.core.scope.ApplicationScope;
import org.apache.usergrid.persistence.index.CandidateResult;
import org.apache.usergrid.persistence.index.GeoCandidateResult;
import org.apache.usergrid.persistence.index.IndexEdge;
import org.apache.usergrid.persistence.index.SearchEdge;
import org.apache.usergrid.persistence.model.entity.Entity;
import org.apache.usergrid.persistence.model.entity.Id;
import org.apache.usergrid.persistence.model.entity.SimpleId;

import com.google.common.base.Preconditions;
import org.elasticsearch.search.SearchHit;


public class IndexingUtils {
Expand Down Expand Up @@ -221,27 +223,42 @@ private static void appendField( final StringBuilder builder, final String type,
}


/**
* Parse the document id into a candidate result
*/
public static CandidateResult parseIndexDocId( final String documentId ) {
public static CandidateResult parseIndexDocId( final SearchHit hit ) {
return parseIndexDocId(hit.getId());
}

public static CandidateResult parseIndexDocId( final SearchHit hit, boolean isGeo ) {

final String documentId = hit.getId();
final double distance = isGeo ? (double) hit.sortValues()[0] : -1;
return parseIndexDocId(documentId,distance);
}

public static CandidateResult parseIndexDocId( final String documentId ) {
return parseIndexDocId(documentId,-1);
}
/**
* Parse the document id into a candidate result
*/
public static CandidateResult parseIndexDocId( final String documentId, final double distance ) {

final Matcher matcher = DOCUMENT_PATTERN.matcher( documentId );
final Matcher matcher = DOCUMENT_PATTERN.matcher(documentId);

Preconditions.checkArgument( matcher.matches(), "Pattern for document id did not match expected format" );
Preconditions.checkArgument( matcher.groupCount() == 9, "9 groups expected in the pattern" );
Preconditions.checkArgument(matcher.matches(), "Pattern for document id did not match expected format");
Preconditions.checkArgument(matcher.groupCount() == 9, "9 groups expected in the pattern");

//Other fields can be parsed using groups. The groups start at value 1, group 0 is the entire match
final String entityUUID = matcher.group( 3 );
final String entityType = matcher.group( 4 );
final String entityUUID = matcher.group(3);
final String entityType = matcher.group(4);

final String versionUUID = matcher.group( 5 );
final String versionUUID = matcher.group(5);


Id entityId = new SimpleId( UUID.fromString( entityUUID ), entityType );
Id entityId = new SimpleId(UUID.fromString(entityUUID), entityType);

return new CandidateResult( entityId, UUID.fromString( versionUUID ), documentId );
return distance >= 0
? new GeoCandidateResult(entityId, UUID.fromString(versionUUID), documentId, distance)
: new CandidateResult(entityId, UUID.fromString(versionUUID), documentId);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,8 @@ private String getSelect( final String select ) {
public Operand getRootOperand() {
return rootOperand;
}

public boolean isGeoQuery(){
return getOriginalQuery().contains("location") && getOriginalQuery().contains("within");
}
}

0 comments on commit 2b1f761

Please sign in to comment.