Skip to content

Commit

Permalink
#494 list institutions in GeoJson format
Browse files Browse the repository at this point in the history
  • Loading branch information
marcos-lg committed Jun 28, 2023
1 parent acd3ece commit 3187898
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 34 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
<owasp-java-html-sanitizer.version>20200713.1</owasp-java-html-sanitizer.version>
<super-csv.version>2.4.0</super-csv.version>
<opencsv.version>5.7.1</opencsv.version>
<geojson.version>1.14</geojson.version>

<!-- Test -->
<junit.version>5.9.1</junit.version>
Expand Down Expand Up @@ -504,6 +505,11 @@
<artifactId>feign-jackson</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>de.grundid.opendatalab</groupId>
<artifactId>geojson-jackson</artifactId>
<version>${geojson.version}</version>
</dependency>

<dependency>
<groupId>com.squareup.retrofit2</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,12 @@
*/
package org.gbif.registry.ws.it.collections.resource;

import liquibase.pro.packaged.I;

import org.gbif.api.model.collections.Institution;
import org.gbif.api.model.collections.InstitutionImportParams;
import org.gbif.api.model.collections.merge.ConvertToCollectionParams;
import org.gbif.api.model.collections.request.InstitutionSearchRequest;
import org.gbif.api.model.collections.suggestions.InstitutionChangeSuggestion;
import org.gbif.api.model.collections.suggestions.Type;
import org.gbif.api.model.common.paging.Pageable;
import org.gbif.api.model.common.paging.PagingRequest;
import org.gbif.api.model.common.paging.PagingResponse;
import org.gbif.api.model.registry.search.collections.KeyCodeNameResult;
Expand All @@ -47,6 +44,9 @@
import java.util.List;
import java.util.UUID;

import org.geojson.Feature;
import org.geojson.FeatureCollection;
import org.geojson.Point;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
Expand Down Expand Up @@ -106,6 +106,27 @@ public void listTest() {
assertEquals(institutions.size(), result.getResults().size());
}

@Test
public void listAsGeoJsonTest() {
FeatureCollection featureCollection = new FeatureCollection();
Feature f1 = new Feature();
f1.setGeometry(new Point(12d, 50d));
f1.setProperty("name", "n1");
f1.setProperty("key", UUID.randomUUID().toString());
featureCollection.add(f1);
Feature f2 = new Feature();
f2.setGeometry(new Point(22d, 51d));
f2.setProperty("name", "n2");
f2.setProperty("key", UUID.randomUUID().toString());
featureCollection.add(f2);

when(institutionService.listGeojson(any(InstitutionSearchRequest.class)))
.thenReturn(featureCollection);

FeatureCollection result = getClient().listAsGeoJson(new InstitutionSearchRequest());
assertEquals(featureCollection.getFeatures().size(), result.getFeatures().size());
}

@Test
public void testSuggest() {
KeyCodeNameResult r1 = new KeyCodeNameResult(UUID.randomUUID(), "c1", "n1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.gbif.registry.service.collections.duplicates.InstitutionDuplicatesService;
import org.gbif.ws.client.filter.SimplePrincipalProvider;

import java.math.BigDecimal;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -49,6 +50,8 @@
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;

import org.geojson.FeatureCollection;
import org.geojson.Point;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

Expand All @@ -58,6 +61,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/** Tests the {@link InstitutionService}. */
public class InstitutionServiceIT extends BaseCollectionEntityServiceIT<Institution> {
Expand Down Expand Up @@ -671,4 +675,45 @@ public void lockMasterSourceFieldsTest() {
institutionService.deleteMasterSourceMetadata(institutionKey);
assertDoesNotThrow(() -> institutionService.delete(institutionKey));
}

@Test
public void listAsGeoJsonTest() {
Institution institution1 = testData.newEntity();
institution1.setCode("c1");
institution1.setName("n1");
institution1.setLatitude(new BigDecimal(12));
institution1.setLongitude(new BigDecimal(2));
UUID key1 = institutionService.create(institution1);

Institution institution2 = testData.newEntity();
institution2.setCode("c2");
institution2.setName("n2");
institution2.setLatitude(new BigDecimal(23));
institution2.setLongitude(new BigDecimal(70));
UUID key2 = institutionService.create(institution2);

Institution institution3 = testData.newEntity();
institution3.setCode("c3");
institution3.setName("n3");
UUID key3 = institutionService.create(institution3);

assertEquals(
2,
institutionService
.listGeojson(InstitutionSearchRequest.builder().build())
.getFeatures()
.size());
FeatureCollection featuresC1 =
institutionService.listGeojson(InstitutionSearchRequest.builder().code("c1").build());
assertEquals(1, featuresC1.getFeatures().size());
assertTrue(featuresC1.getFeatures().get(0).getGeometry() instanceof Point);
assertEquals(2, featuresC1.getFeatures().get(0).getProperties().size());
assertEquals("n1", featuresC1.getFeatures().get(0).getProperty("name"));
assertEquals(
12d,
((Point) featuresC1.getFeatures().get(0).getGeometry()).getCoordinates().getLatitude());
assertEquals(
2d,
((Point) featuresC1.getFeatures().get(0).getGeometry()).getCoordinates().getLongitude());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.gbif.registry.persistence.mapper.collections.dto.CollectionMatchedDto;
import org.gbif.registry.persistence.mapper.collections.dto.DuplicateDto;
import org.gbif.registry.persistence.mapper.collections.dto.DuplicateMetadataDto;
import org.gbif.registry.persistence.mapper.collections.dto.InstitutionGeoJsonDto;
import org.gbif.registry.persistence.mapper.collections.dto.InstitutionMatchedDto;
import org.gbif.registry.persistence.mapper.collections.dto.MasterSourceOrganizationDto;
import org.gbif.registry.persistence.mapper.collections.dto.SearchDto;
Expand Down Expand Up @@ -156,6 +157,9 @@ ConfigurationCustomizer mybatisConfigCustomizer() {
configuration
.getTypeAliasRegistry()
.registerAlias("InstitutionMatchedDto", InstitutionMatchedDto.class);
configuration
.getTypeAliasRegistry()
.registerAlias("InstitutionGeoJsonDto", InstitutionGeoJsonDto.class);
configuration
.getTypeAliasRegistry()
.registerAlias("CollectionMatchedDto", CollectionMatchedDto.class);
Expand All @@ -165,7 +169,9 @@ ConfigurationCustomizer mybatisConfigCustomizer() {
configuration
.getTypeAliasRegistry()
.registerAlias("MasterSourceOrganizationDto", MasterSourceOrganizationDto.class);
configuration.getTypeAliasRegistry().registerAlias("OrganizationContactDto", OrganizationContactDto.class);
configuration
.getTypeAliasRegistry()
.registerAlias("OrganizationContactDto", OrganizationContactDto.class);

configuration.getTypeAliasRegistry().registerAlias("UriTypeHandler", UriTypeHandler.class);
configuration.getTypeAliasRegistry().registerAlias("UuidTypeHandler", UuidTypeHandler.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
package org.gbif.registry.persistence.mapper.collections;

import org.gbif.api.model.collections.Institution;
import org.gbif.api.model.common.paging.Pageable;
import org.gbif.api.model.registry.search.collections.KeyCodeNameResult;
import org.gbif.registry.persistence.mapper.collections.dto.InstitutionGeoJsonDto;
import org.gbif.registry.persistence.mapper.collections.dto.InstitutionMatchedDto;
import org.gbif.registry.persistence.mapper.collections.params.InstitutionSearchParams;

Expand Down Expand Up @@ -48,4 +48,6 @@ public interface InstitutionMapper

void convertToCollection(
@Param("institutionKey") UUID institutionKey, @Param("collectionKey") UUID collectionKey);

List<InstitutionGeoJsonDto> listGeoJson(@Param("params") InstitutionSearchParams searchParams);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.gbif.registry.persistence.mapper.collections.dto;

import java.math.BigDecimal;
import java.util.UUID;

import lombok.Data;

@Data
public class InstitutionGeoJsonDto {

private UUID key;
private String name;
private BigDecimal latitude;
private BigDecimal longitude;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<result property="alternativeCodes" column="alternative_codes" typeHandler="org.gbif.mybatis.type.StringArrayTypeHandler"/>
</resultMap>

<resultMap id="INSTITUTION_GEOJSON_DTO_MAP" type="InstitutionGeoJsonDto" autoMapping="true">
<id property="key" column="key"/>
</resultMap>

<sql id="INSTITUTION_WRITE_FIELDS">
key, code, name, description, type, active, email, phone, homepage, catalog_url, api_url, institutional_governance, discipline,
latitude, longitude, mailing_address_key, address_key, additional_names, founding_date, geographic_description,
Expand Down Expand Up @@ -207,6 +211,22 @@
<include refid="LIST_FILTER"/>
</select>

<!-- returns only the fields needed for geojson -->
<select id="listGeoJson" resultType="InstitutionGeoJsonDto" resultMap="INSTITUTION_GEOJSON_DTO_MAP" parameterType="Pageable">
SELECT DISTINCT ON (<if test="params.query != null" >ts_rank_cd(i.fulltext_search, query), </if>
<if test="params.fuzzyName != null" >similarity_score, </if> i.key)
i.key, i.name, i.latitude, i.longitude
<if test="params.fuzzyName != null" >,similarity(i.name, #{params.fuzzyName,jdbcType=VARCHAR}) AS similarity_score</if>
<include refid="LIST_FILTER"/>
AND i.latitude IS NOT NULL AND i.longitude IS NOT NULL
ORDER BY <if test="params.query != null" >ts_rank_cd(i.fulltext_search, query) DESC, </if>
<if test="params.fuzzyName != null" >similarity_score DESC, </if>
i.key
<if test="params.page != null" >
LIMIT #{params.page.limit} OFFSET #{params.page.offset}
</if>
</select>

<sql id="LIST_FILTER">
FROM institution i
<if test="params.query != null" >
Expand Down
4 changes: 4 additions & 0 deletions registry-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
</dependency>
<dependency>
<groupId>de.grundid.opendatalab</groupId>
<artifactId>geojson-jackson</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.gbif.registry.persistence.mapper.collections.InstitutionMapper;
import org.gbif.registry.persistence.mapper.collections.MasterSourceSyncMetadataMapper;
import org.gbif.registry.persistence.mapper.collections.OccurrenceMappingMapper;
import org.gbif.registry.persistence.mapper.collections.dto.InstitutionGeoJsonDto;
import org.gbif.registry.persistence.mapper.collections.params.InstitutionSearchParams;
import org.gbif.registry.service.WithMyBatis;
import org.gbif.registry.service.collections.converters.InstitutionConverter;
Expand All @@ -53,6 +54,9 @@
import javax.validation.Validator;
import javax.validation.groups.Default;

import org.geojson.Feature;
import org.geojson.FeatureCollection;
import org.geojson.Point;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
Expand Down Expand Up @@ -128,39 +132,43 @@ private PagingResponse<Institution> listInternal(

Pageable page = searchRequest.getPage() == null ? new PagingRequest() : searchRequest.getPage();

InstitutionSearchParams params = buildSearchParams(searchRequest, deleted, page);

long total = institutionMapper.count(params);
return new PagingResponse<>(page, total, institutionMapper.list(params));
}

private InstitutionSearchParams buildSearchParams(
InstitutionSearchRequest searchRequest, boolean deleted, Pageable page) {
String query =
searchRequest.getQ() != null
? Strings.emptyToNull(CharMatcher.WHITESPACE.trimFrom(searchRequest.getQ()))
: searchRequest.getQ();

InstitutionSearchParams params =
InstitutionSearchParams.builder()
.query(query)
.code(searchRequest.getCode())
.name(searchRequest.getName())
.alternativeCode(searchRequest.getAlternativeCode())
.machineTagNamespace(searchRequest.getMachineTagNamespace())
.machineTagName(searchRequest.getMachineTagName())
.machineTagValue(searchRequest.getMachineTagValue())
.identifierType(searchRequest.getIdentifierType())
.identifier(searchRequest.getIdentifier())
.country(searchRequest.getCountry())
.city(searchRequest.getCity())
.fuzzyName(searchRequest.getFuzzyName())
.active(searchRequest.getActive())
.type(searchRequest.getType())
.institutionalGovernance(searchRequest.getInstitutionalGovernance())
.disciplines(searchRequest.getDisciplines())
.masterSourceType(searchRequest.getMasterSourceType())
.numberSpecimens(parseNumberSpecimensParameter(searchRequest.getNumberSpecimens()))
.displayOnNHCPortal(searchRequest.getDisplayOnNHCPortal())
.replacedBy(searchRequest.getReplacedBy())
.deleted(deleted)
.page(page)
.build();

long total = institutionMapper.count(params);
return new PagingResponse<>(page, total, institutionMapper.list(params));
return InstitutionSearchParams.builder()
.query(query)
.code(searchRequest.getCode())
.name(searchRequest.getName())
.alternativeCode(searchRequest.getAlternativeCode())
.machineTagNamespace(searchRequest.getMachineTagNamespace())
.machineTagName(searchRequest.getMachineTagName())
.machineTagValue(searchRequest.getMachineTagValue())
.identifierType(searchRequest.getIdentifierType())
.identifier(searchRequest.getIdentifier())
.country(searchRequest.getCountry())
.city(searchRequest.getCity())
.fuzzyName(searchRequest.getFuzzyName())
.active(searchRequest.getActive())
.type(searchRequest.getType())
.institutionalGovernance(searchRequest.getInstitutionalGovernance())
.disciplines(searchRequest.getDisciplines())
.masterSourceType(searchRequest.getMasterSourceType())
.numberSpecimens(parseNumberSpecimensParameter(searchRequest.getNumberSpecimens()))
.displayOnNHCPortal(searchRequest.getDisplayOnNHCPortal())
.replacedBy(searchRequest.getReplacedBy())
.deleted(deleted)
.page(page)
.build();
}

@Override
Expand Down Expand Up @@ -225,4 +233,23 @@ public UUID createFromOrganization(UUID organizationKey, String institutionCode)

return institutionKey;
}

@Override
public FeatureCollection listGeojson(InstitutionSearchRequest searchRequest) {
List<InstitutionGeoJsonDto> dtos =
institutionMapper.listGeoJson(buildSearchParams(searchRequest, false, null));

FeatureCollection featureCollection = new FeatureCollection();
dtos.forEach(
dto -> {
Feature feature = new Feature();
feature.setProperty("key", dto.getKey());
feature.setProperty("name", dto.getName());
feature.setGeometry(
new Point(dto.getLongitude().doubleValue(), dto.getLatitude().doubleValue()));
featureCollection.add(feature);
});

return featureCollection;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
import org.gbif.api.model.collections.merge.ConvertToCollectionParams;
import org.gbif.api.model.collections.request.InstitutionSearchRequest;
import org.gbif.api.model.collections.suggestions.InstitutionChangeSuggestion;
import org.gbif.api.model.common.paging.Pageable;
import org.gbif.api.model.common.paging.PagingResponse;
import org.gbif.api.model.registry.search.collections.KeyCodeNameResult;

import java.util.List;
import java.util.UUID;

import org.geojson.FeatureCollection;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -42,6 +42,13 @@ public interface InstitutionClient
@ResponseBody
PagingResponse<Institution> list(@SpringQueryMap InstitutionSearchRequest searchRequest);

@RequestMapping(
method = RequestMethod.GET,
value = "geojson",
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
FeatureCollection listAsGeoJson(@SpringQueryMap InstitutionSearchRequest searchRequest);

@RequestMapping(
method = RequestMethod.GET,
value = "deleted",
Expand Down
Loading

0 comments on commit 3187898

Please sign in to comment.