...
 
Commits (2)
......@@ -357,9 +357,35 @@
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
<version>4.3</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-sql-spatial</artifactId>
<version>${querydsl.version}</version>
</dependency>
<!--<dependency>-->
<!--<groupId>org.hsqldb</groupId>-->
<!--<artifactId>hsqldb</artifactId>-->
<!--<version>${hsqldb.version}</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.orbisgis</groupId>
<artifactId>h2gis-functions</artifactId>
<version>1.3.2</version>
<scope>test</scope>
</dependency>
<dependency>
......@@ -537,6 +563,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.lonnyj</groupId>
<artifactId>liquibase-spatial</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>glis-client-resttemplate</artifactId>
......@@ -992,5 +1024,13 @@
<id>snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
<repository>
<id>OSGEO GeoTools repo</id>
<url>http://download.osgeo.org/webdav/geotools</url>
</repository>
<repository>
<id>Hibernate Spatial repo</id>
<url>http://www.hibernatespatial.org/repository</url>
</repository>
</repositories>
</project>
......@@ -93,7 +93,11 @@ public class ShortFilterServiceImpl implements ShortFilterService {
final Map<String, Object> map = (Map<String, Object>) filterObject;
map.forEach((key, value) -> {
if (value instanceof List) {
Collections.sort((List) value);
if (! ((List) value).isEmpty()) {
if (! (((List) value).get(0) instanceof List)) {
Collections.sort((List) value);
}
}
} else if (value instanceof Map) {
sortFilterValues(value);
}
......
......@@ -33,6 +33,7 @@ import com.querydsl.core.types.SubQueryExpression;
import com.querydsl.core.types.TemplateExpression;
import com.querydsl.core.types.Visitor;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.spatial.SpatialOps;
public class ElasticQueryBuilder implements Visitor<Void, Void> {
private static Logger LOG = LoggerFactory.getLogger(ElasticQueryBuilder.class);
......@@ -149,6 +150,15 @@ public class ElasticQueryBuilder implements Visitor<Void, Void> {
Path<?> path = (Path<?>) args.get(0);
PathMetadata pmd = path.getMetadata();
mustNotClauses.add(existsQuery(customizedPath(pmd.getParent().toString() + "." + pmd.getName())));
} else if (operator == SpatialOps.WITHIN) {
LOG.debug("WITHIN: {}", args);
for (Expression<?> expr : args) {
printExpression("WITHIN.. ", expr);
}
Path<?> a0 = (Path<?>) args.get(0);
Expression<?> a1 = args.get(1);
handleWithin(a0, a1);
} else {
LOG.error("Op {}: {}", operator, args);
}
......@@ -207,6 +217,16 @@ public class ElasticQueryBuilder implements Visitor<Void, Void> {
mustClauses.add(termsQuery(customizedPath(getParentPath(pmd.getParent()) + "." + pmd.getName()), toValues(value)));
}
}
private void handleWithin(Path<?> path, Expression<?> value) {
PathMetadata pmd = path.getMetadata();
if (pmd.getPathType() == PathType.COLLECTION_ANY) {
LOG.debug("Path ANY for {}={}", pmd.getParent(), value);
mustClauses.add(termsQuery(customizedPath(pmd.getParent().toString()), toValues(value)));
} else {
mustClauses.add(termsQuery(customizedPath(getParentPath(pmd.getParent()) + "." + pmd.getName()), toValues(value)));
}
}
private String getParentPath(Path<?> path) {
String pathValue = path.toString();
......
/**
* Copyright 2014 Global Crop Diversity Trust
/*
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,7 +12,7 @@
* 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.genesys2.server.model.genesys;
......@@ -29,7 +29,9 @@ import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Version;
import org.apache.commons.lang3.StringUtils;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import org.genesys.blocks.auditlog.annotations.Audited;
import org.genesys.blocks.model.BasicModel;
import org.genesys.blocks.model.JsonViews;
......@@ -37,6 +39,7 @@ import org.genesys.blocks.model.SelfCleaning;
import org.genesys.worldclim.WorldClimUtil;
import org.genesys2.server.model.impl.GeoReferencedEntity;
import org.genesys2.server.model.impl.TileClimate;
import org.hibernate.annotations.Type;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
......@@ -51,7 +54,8 @@ import com.fasterxml.jackson.annotation.JsonView;
public class AccessionGeo extends BasicModel implements GeoReferencedEntity, AccessionRelated, SelfCleaning {
private static final long serialVersionUID = 8046638388176612388L;
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
@Version
private long version = 0;
......@@ -65,6 +69,11 @@ public class AccessionGeo extends BasicModel implements GeoReferencedEntity, Acc
@Column(name = "elevation")
private Double elevation;
@JsonIgnore
@Type(type = "org.hibernate.spatial.GeometryType")
@Column(name = "georef", nullable = false)
private Point location;
private Double uncertainty;
@Column(length = 100)
private String datum;
......@@ -100,6 +109,14 @@ public class AccessionGeo extends BasicModel implements GeoReferencedEntity, Acc
this.method = null;
}
if (this.longitude != null && this.latitude != null) {
this.location = GEOMETRY_FACTORY.createPoint(new Coordinate(this.longitude, this.latitude));
} else {
// this.location = null;
// FIXME mysql is dumb: All parts of a SPATIAL index must be NOT NULL.
this.location = GEOMETRY_FACTORY.createPoint(new Coordinate(-666, -666));
}
tileIndex = WorldClimUtil.getWorldclim25Tile(this.longitude, this.latitude);
}
......@@ -147,6 +164,14 @@ public class AccessionGeo extends BasicModel implements GeoReferencedEntity, Acc
this.elevation = elevation;
}
public Point getLocation() {
return location;
}
public void setLocation(Point location) {
this.location = location;
}
public void setUncertainty(final Double uncertainty) {
this.uncertainty = uncertainty;
}
......
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -17,6 +17,9 @@ package org.genesys2.server.service.filter;
import static org.genesys2.server.model.genesys.QAccessionGeo.accessionGeo;
import java.util.List;
import java.util.stream.Collectors;
import org.genesys.blocks.model.filters.BasicModelFilter;
import org.genesys.blocks.model.filters.NumberFilter;
import org.genesys2.server.model.genesys.AccessionGeo;
......@@ -24,11 +27,15 @@ import org.genesys2.server.model.genesys.QAccessionGeo;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
/**
* The Class CountryFilter.
*/
public class AccessionGeoFilter extends BasicModelFilter<AccessionGeoFilter, AccessionGeo> {
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
/** The longitude. */
public NumberFilter<Double> longitude;
......@@ -44,6 +51,9 @@ public class AccessionGeoFilter extends BasicModelFilter<AccessionGeoFilter, Acc
/** The climate. */
public ClimateFilter climate;
/** The polygon. */
public List<Double[]> polygon;
/**
* Builds the query.
*
......@@ -84,8 +94,19 @@ public class AccessionGeoFilter extends BasicModelFilter<AccessionGeoFilter, Acc
if (climate != null) {
and.and(climate.buildQuery(accessiongeo.climate));
}
if (polygon != null && polygon.size() > 2) {
and.and(accessiongeo.location.within(toPolygon(polygon)));
}
return and;
}
private static Polygon toPolygon(List<Double[]> polygon) {
List<Coordinate> arr = polygon.stream().map(p -> new Coordinate(p[0], p[1])).collect(Collectors.toList());
// close loop if not closed
if (! arr.get(arr.size() - 1).equals2D(arr.get(0))) {
arr.add(arr.get(0));
}
return GEOMETRY_FACTORY.createPolygon(arr.toArray(new Coordinate[] {}));
}
}
/*
* Copyright 2019 Global Crop Diversity Trust
*
* 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.genesys2.util;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.spatial.dialect.h2geodb.GeoDBDialect;
import org.hibernate.type.DoubleType;
public class BetterGeoDBDialect extends GeoDBDialect {
public BetterGeoDBDialect() {
super();
registerFunction("stddev", new StandardSQLFunction("stddev_pop", DoubleType.INSTANCE));
}
}
......@@ -55,7 +55,7 @@ db.username=root
db.password=
db.showSql=false
db.hbm2ddl=false
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.dialect=org.hibernate.spatial.dialect.mysql.MySQLSpatial56Dialect
# bit.ly shortener
# TODO change access token and guid
......
......@@ -5578,3 +5578,33 @@ databaseChangeLog:
name: createdDate
indexName: IX_accession_createdDate
tableName: accession
- changeSet:
id: 1550231443014-5
author: mborodenko & mobreza
changes:
- addColumn:
columns:
- column:
constraints:
nullable: true
name: georef
type: point
tableName: accession_geo
- sql:
sql: >-
update accession_geo set georef = POINT(latitude, longitude) where latitude is not null and longitude is not null
- sql:
sql: >-
update accession_geo set georef = POINT(-666, -666) where latitude is null or longitude is null
- addNotNullConstraint:
columnName: georef
columnDataType: point
tableName: accession_geo
- createSpatialIndex:
geometryType: Point
columns:
- column:
name: georef
indexName: IX_accessiongeo_georef
tableName: accession_geo
......@@ -41,6 +41,7 @@ import org.genesys2.server.persistence.FaoInstituteRepository;
import org.genesys2.server.persistence.Taxonomy2Repository;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.TaxonomyService;
import org.genesys2.server.service.filter.AccessionFilter;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
......@@ -316,6 +317,39 @@ public class AccessionControllerTest extends AbstractApiTest {
assertThat("breederCode must be an empty array", result.getAccessionId().getBreederCode().size(), is(0));
}
@Test
public void testFindAccessionInsidePolygon() throws Exception {
// specify points of polygon
List<Double[]> pointsList = new ArrayList<>();
pointsList.add(new Double[]{ 80D, 70D });
pointsList.add(new Double[]{ 80D, 90D });
pointsList.add(new Double[]{ 60D, 90D });
pointsList.add(new Double[]{ 60D, 70D });
pointsList.add(new Double[]{ 80D, 70D });
AccessionFilter af = new AccessionFilter();
af.geo().polygon = pointsList;
// persist accessions with lat and long outside the polygon
accessionRepository.save(addAccessionInDB(81D, 75D));
accessionRepository.save(addAccessionInDB(81D, 69D));
// persist accession with lat and long inside the polygon
Accession insideAccession = accessionRepository.save(addAccessionInDB(75D, 75D));
assertThat(accessionRepository.count(), is(3L));
assertThat(accessionGeoRepository.count(), is(3L));
mockMvc.perform(post(AccessionController.CONTROLLER_URL + "/list")
.contentType(MediaType.APPLICATION_JSON)
.content(af.toString()))
.andExpect(status().isOk())
// .andDo(MockMvcResultHandlers.print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", is(notNullValue())))
.andExpect(jsonPath("$.content", hasSize(1)))
.andExpect(jsonPath("$.content[0].uuid", is(insideAccession.getUuid().toString())));
}
@Test
public void testInvalidBreederCode() throws Exception {
......@@ -488,11 +522,15 @@ public class AccessionControllerTest extends AbstractApiTest {
}
private Accession addAccessionInDB() {
return addAccessionInDB(75D, 75D);
}
private Accession addAccessionInDB(Double longitude, Double latitude) {
Accession a = new Accession();
a.setAccessionId(new AccessionId());
AccessionGeo accessionGeo = new AccessionGeo();
accessionGeo.setLongitude(75d);
accessionGeo.setLatitude(75d);
accessionGeo.setLongitude(longitude);
accessionGeo.setLatitude(latitude);
a.getAccessionId().setGeo(accessionGeo);
a.setInstitute(institute);
a.setAccessionNumber("ACC" + System.currentTimeMillis() + "-" + acceNumb.incrementAndGet());
......
......@@ -313,7 +313,7 @@ public class PartnerControllerTest extends AbstractApiTest {
this.mockMvc
.perform(RestDocumentationRequestBuilders.post(PartnerController.CONTROLLER_URL.concat("/addInstitutes/{UUID}"), partner.getUuid())
.content(verboseMapper.writeValueAsString(instituteList.stream().map(FaoInstitute::getCode).collect(Collectors.toList())))
.content(verboseMapper.writeValueAsString(instituteList.stream().map(inst -> inst.getCode().toLowerCase()).collect(Collectors.toList())))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
// .andDo(MockMvcResultHandlers.print())
......@@ -339,7 +339,7 @@ public class PartnerControllerTest extends AbstractApiTest {
Assert.assertEquals(instituteList.get(0).getOwner(), partner);
Assert.assertEquals(instituteList.get(1).getOwner(), partner);
String[] instCodesToRemove = { instituteList.get(0).getCode() };
String[] instCodesToRemove = { instituteList.get(0).getCode().toLowerCase() };
this.mockMvc
.perform(RestDocumentationRequestBuilders.post(PartnerController.CONTROLLER_URL.concat("/removeInstitutes/{UUID}"), partner.getUuid())
......
......@@ -15,14 +15,14 @@
#-------------------------------------------------------------------------------
#DB connection properties
#In-memory HSQLDB
db.url=jdbc:hsqldb:mem:test;sql.syntax_mys=true;sql.ignore_case=true
db.driverClassName = org.hsqldb.jdbc.JDBCDriver
#In-memory H2 Database
db.url=jdbc:h2:mem:test;MODE=MYSQL;IGNORECASE=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:scripts/init_geo_funcs.sql'
db.driverClassName=org.h2.Driver
db.username = sa
db.password =
db.showSql=false
db.hbm2ddl=true
hibernate.dialect=org.genesys.blocks.util.BetterHSQLDialect
hibernate.dialect=org.genesys2.util.BetterGeoDBDialect
#Paginator
paginator.default.maxPageSize=500
......
CREATE ALIAS IF NOT EXISTS H2GIS_SPATIAL FOR "org.h2gis.functions.factory.H2GISFunctions.load";
CALL H2GIS_SPATIAL();
\ No newline at end of file