From 31878980040b7818863facb322d760c19d6e2043 Mon Sep 17 00:00:00 2001 From: Marcos Lopez Gonzalez Date: Wed, 28 Jun 2023 16:41:02 +0200 Subject: [PATCH] #494 list institutions in GeoJson format --- pom.xml | 6 ++ .../resource/InstitutionResourceIT.java | 27 +++++- .../service/InstitutionServiceIT.java | 45 ++++++++++ .../config/MyBatisConfiguration.java | 8 +- .../mapper/collections/InstitutionMapper.java | 4 +- .../dto/InstitutionGeoJsonDto.java | 15 ++++ .../mapper/collections/InstitutionMapper.xml | 20 +++++ registry-service/pom.xml | 4 + .../DefaultInstitutionService.java | 83 ++++++++++++------- .../client/collections/InstitutionClient.java | 9 +- .../collections/InstitutionResource.java | 18 ++++ 11 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/InstitutionGeoJsonDto.java diff --git a/pom.xml b/pom.xml index 3aa98f0de..5801fd4ee 100644 --- a/pom.xml +++ b/pom.xml @@ -127,6 +127,7 @@ 20200713.1 2.4.0 5.7.1 + 1.14 5.9.1 @@ -504,6 +505,11 @@ feign-jackson ${feign.version} + + de.grundid.opendatalab + geojson-jackson + ${geojson.version} + com.squareup.retrofit2 diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java index b6e5f1543..cb7760abb 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java @@ -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; @@ -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; @@ -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"); diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java index eb049a7b6..3fffb044f 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java @@ -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; @@ -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; @@ -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 { @@ -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()); + } } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java index 089ba9c2c..4ac8154ce 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java @@ -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; @@ -156,6 +157,9 @@ ConfigurationCustomizer mybatisConfigCustomizer() { configuration .getTypeAliasRegistry() .registerAlias("InstitutionMatchedDto", InstitutionMatchedDto.class); + configuration + .getTypeAliasRegistry() + .registerAlias("InstitutionGeoJsonDto", InstitutionGeoJsonDto.class); configuration .getTypeAliasRegistry() .registerAlias("CollectionMatchedDto", CollectionMatchedDto.class); @@ -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); diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java index 2f6ba82dc..400e1f4b6 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java @@ -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; @@ -48,4 +48,6 @@ public interface InstitutionMapper void convertToCollection( @Param("institutionKey") UUID institutionKey, @Param("collectionKey") UUID collectionKey); + + List listGeoJson(@Param("params") InstitutionSearchParams searchParams); } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/InstitutionGeoJsonDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/InstitutionGeoJsonDto.java new file mode 100644 index 000000000..8231e4ff0 --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/InstitutionGeoJsonDto.java @@ -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; +} diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml index 6b7653069..682784780 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml @@ -26,6 +26,10 @@ + + + + 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, @@ -207,6 +211,22 @@ + + + FROM institution i diff --git a/registry-service/pom.xml b/registry-service/pom.xml index 76950f430..db3cc3103 100644 --- a/registry-service/pom.xml +++ b/registry-service/pom.xml @@ -64,6 +64,10 @@ com.opencsv opencsv + + de.grundid.opendatalab + geojson-jackson + diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java index a2c2a087c..82cf02f64 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java @@ -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; @@ -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; @@ -128,39 +132,43 @@ private PagingResponse 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 @@ -225,4 +233,23 @@ public UUID createFromOrganization(UUID organizationKey, String institutionCode) return institutionKey; } + + @Override + public FeatureCollection listGeojson(InstitutionSearchRequest searchRequest) { + List 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; + } } diff --git a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/InstitutionClient.java b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/InstitutionClient.java index 001bd55c3..70697dcf3 100644 --- a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/InstitutionClient.java +++ b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/InstitutionClient.java @@ -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; @@ -42,6 +42,13 @@ public interface InstitutionClient @ResponseBody PagingResponse 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", diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/InstitutionResource.java b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/InstitutionResource.java index fe938f422..dd8205cc4 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/InstitutionResource.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/InstitutionResource.java @@ -51,6 +51,7 @@ import javax.servlet.http.HttpServletResponse; +import org.geojson.FeatureCollection; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; @@ -238,6 +239,23 @@ public PagingResponse list(InstitutionSearchRequest searchRequest) return institutionService.list(searchRequest); } + @Operation( + operationId = "listInstitutionsGeoJson", + summary = "List all institutions in GeoJson format", + description = + "Lists all current institutions in GeoJson format (deleted institutions are not listed).", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0101"))) + @InstitutionSearchParameters + @ApiResponse(responseCode = "200", description = "Institution search successful") + @ApiResponse(responseCode = "400", description = "Invalid search query provided") + @GetMapping("geojson") + public FeatureCollection listAsGeoJson(InstitutionSearchRequest searchRequest) { + return institutionService.listGeojson(searchRequest); + } + private String getExportFileHeader(InstitutionSearchRequest searchRequest, ExportFormat format) { String preFileName = CsvWriter.notNullJoiner(