Skip to content

Commit

Permalink
API / Extent / Add geometry collection support. (#7911)
Browse files Browse the repository at this point in the history
* API / Extent / Add geometry collection support.

To make full view more consistent with the default view, add support for
geometry collection to the extent API. This allows to have all bounding
boxes in the same map in the side panel.

eg.
http://localhost:8080/geonetwork/srv/api/regions/geom.png?geomsrs=EPSG:4326&geom=GEOMETRYCOLLECTION(POINT(-32.277667%2037.291833),%20POINT(-44.949833%2023.367667),%20POINT(-31.556167%2037.841167))&width=600&strokeColor=255,255,0,255
http://localhost:8080/geonetwork/srv/api/regions/geom.png?geomsrs=EPSG:4326&geom=GEOMETRYCOLLECTION(POLYGON((10.5253%206.7632,10.5253%2023.2401,-18.3746%2023.2401,-18.3746%206.7632,10.5253%206.7632)),POLYGON((-28.8471%20-34.1253,-28.8471%205.2718,-73.9828%205.2718,-73.9828%20-34.1253,-28.8471%20-34.1253)))
http://localhost:8080/geonetwork/srv/api/regions/geom.png?geomsrs=EPSG:4326&geom=GEOMETRYCOLLECTION(POINT(2.4%2048.5),POINT(29%2030),POINT(33%2033))

Also improve point visibility based on image size and add stroke/fill color parameter similar to record extent API.

Follow up of #7882

Funded by Ifremer

* Update web/src/main/webapp/WEB-INF/data/data/formatter/xslt/render-functions.xsl

Co-authored-by: Jose García <[email protected]>

---------

Co-authored-by: Jose García <[email protected]>
  • Loading branch information
fxprunayre and josegar74 authored May 6, 2024
1 parent e13c93b commit 4ff5da2
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,7 @@
<xsl:copy-of select="gn-fn-render:extent($metadataUuid)"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates mode="render-field"
select=".//mdb:identificationInfo/*/mri:extent//gex:EX_GeographicBoundingBox">
</xsl:apply-templates>
<xsl:copy-of select="gn-fn-render:bboxes(.//mdb:identificationInfo/*/mri:extent//gex:EX_GeographicBoundingBox)"/>
</xsl:otherwise>
</xsl:choose>
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,7 @@
<xsl:copy-of select="gn-fn-render:extent($metadataUuid)"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates mode="render-field"
select=".//gmd:EX_GeographicBoundingBox">
</xsl:apply-templates>
<xsl:copy-of select="gn-fn-render:bboxes(.//gmd:EX_GeographicBoundingBox)"/>
</xsl:otherwise>
</xsl:choose>
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.locationtech.jts.awt.IdentityPointTransformation;
import org.locationtech.jts.awt.PointShapeFactory;
import org.locationtech.jts.awt.PointTransformation;
import org.locationtech.jts.awt.ShapeWriter;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.geotools.api.metadata.extent.Extent;
Expand All @@ -47,13 +51,16 @@

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Map;
import java.util.SortedSet;

import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
import static org.locationtech.jts.geom.Geometry.TYPENAME_GEOMETRYCOLLECTION;
import static org.locationtech.jts.geom.Geometry.TYPENAME_POINT;

public class MapRenderer {

Expand Down Expand Up @@ -166,7 +173,10 @@ public BufferedImage render(String id, String srs, Integer width, Integer height
}
}
BufferedImage image;
boolean isPoint = geom.getGeometryType().equals("Point");
boolean isPoint = geom.getGeometryType().equals(TYPENAME_POINT)
|| (geom.getGeometryType().equals(TYPENAME_GEOMETRYCOLLECTION)
&& geom.getNumGeometries() == 1
&& geom.getGeometryN(0).getGeometryType().equals(TYPENAME_POINT));
int pointBufferSize = 150;

Envelope bboxOfImage = new Envelope(isPoint ?
Expand Down Expand Up @@ -213,10 +223,14 @@ public BufferedImage render(String id, String srs, Integer width, Integer height
if (error != null) {
graphics.drawString(error.getMessage(), 0, imageDimensions.height / 2);
}
ShapeWriter shapeWriter = new ShapeWriter();
Color geomFillColor = getColor(fillColor, new Color(0, 0, 0, 50));

Color geomFillColor = getColor(fillColor, new Color(0, 0, 0, 30));
Color geomStrokeColor = getColor(strokeColor, new Color(0, 0, 0, 255));
AffineTransform worldToScreenTransform = worldToScreenTransform(bboxOfImage, imageDimensions);
int pointSize = 5;
ShapeWriter shapeWriter = new ShapeWriter(new IdentityPointTransformation(),
new PointShapeFactory.Circle(pointSize * bboxOfImage.getWidth() / imageDimensions.getWidth()));

for (int i = 0; i < geom.getNumGeometries(); i++) {
Geometry geomExtent = MapRenderer.getGeometryExtent(geom.getGeometryN(i), srs, useGeodesicExtents);
// draw each included geometry separately to ensure they are filled correctly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ public class MetadataExtentApi {
private static final String API_PARAM_WIDTH_DESCRIPTION = "(optional) width of the image that is created. Only one of width and height are permitted";
private static final String API_PARAM_HEIGHT_DESCRIPTION = "(optional) height of the image that is created. Only one of width and height are permitted";
private static final String API_PARAM_BG_DESCRIPTION = "(optional) URL for loading a background image for regions or a key that references the namedBackgrounds (configured in config-spring-geonetwork.xml). A WMS GetMap request is the typical example. The URL must be parameterized with the following parameters: minx, maxx, miny, maxy, width, height";
private static final String API_PARAM_FILL_DESCRIPTION = "(optional) Fill color with format RED,GREEN,BLUE,ALPHA";
private static final String API_PARAM_STROKE_DESCRIPTION = "(optional) Stroke color with format RED,GREEN,BLUE,ALPHA";
public static final String API_PARAM_FILL_DESCRIPTION = "(optional) Fill color with format RED,GREEN,BLUE,ALPHA";
public static final String API_PARAM_STROKE_DESCRIPTION = "(optional) Stroke color with format RED,GREEN,BLUE,ALPHA";

private static final String EXTENT_XPATH = ".//*[local-name() ='extent']/*/*[local-name() = 'geographicElement']/*";
private static final String EXTENT_DESCRIPTION_XPATH = "ancestor::*[local-name() = 'EX_Extent']/*[local-name() = 'description']/*/text()";
Expand Down Expand Up @@ -139,7 +139,7 @@ public HttpEntity<byte[]> getAllRecordExtentAsImage(
@Parameter(description = API_PARAM_BG_DESCRIPTION)
@RequestParam(value = BACKGROUND_PARAM, required = false, defaultValue = "settings") String background,
@Parameter(description = API_PARAM_FILL_DESCRIPTION)
@RequestParam(value = "", required = false, defaultValue = "0,0,0,50")
@RequestParam(value = "", required = false, defaultValue = "0,0,0,30")
String fillColor,
@Parameter(description = API_PARAM_STROKE_DESCRIPTION)
@RequestParam(value = "", required = false, defaultValue = "0,0,0,255")
Expand Down Expand Up @@ -251,7 +251,7 @@ public HttpEntity<byte[]> getOneRecordExtentAsImage(
@Parameter(description = API_PARAM_BG_DESCRIPTION)
@RequestParam(value = BACKGROUND_PARAM, required = false, defaultValue = "settings") String background,
@Parameter(description = API_PARAM_FILL_DESCRIPTION)
@RequestParam(value = "", required = false, defaultValue = "0,0,0,50")
@RequestParam(value = "", required = false, defaultValue = "0,0,0,30")
String fillColor,
@Parameter(description = API_PARAM_STROKE_DESCRIPTION)
@RequestParam(value = "", required = false, defaultValue = "0,0,0,255")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ public HttpEntity<byte[]> getGeomAsImage(
@RequestParam(value = GEOM_TYPE_PARAM, defaultValue = "WKT") String geomType,
@Parameter(description = "")
@RequestParam(value = GEOM_SRS_PARAM, defaultValue = "EPSG:4326") String geomSrs,
@Parameter(description = API_PARAM_FILL_DESCRIPTION)
@RequestParam(value = "", required = false, defaultValue = "0,0,0,30")
String fillColor,
@Parameter(description = API_PARAM_STROKE_DESCRIPTION)
@RequestParam(value = "", required = false, defaultValue = "0,0,0,255")
String strokeColor,
@Parameter(hidden = true)
NativeWebRequest nativeWebRequest,
@Parameter(hidden = true)
Expand Down Expand Up @@ -268,7 +274,7 @@ public HttpEntity<byte[]> getGeomAsImage(
}

MapRenderer renderer = new MapRenderer(context);
BufferedImage image = renderer.render(regionId, srs, width, height, background, geomParam, geomType, geomSrs, null, null);
BufferedImage image = renderer.render(regionId, srs, width, height, background, geomParam, geomType, geomSrs, fillColor, strokeColor);

if (image == null) return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@

@ContextConfiguration(inheritLocations = true, locations = "classpath:extents-test-context.xml")
public class MetadataExtentApiTest extends AbstractServiceIntegrationTest {
private static boolean DO_NOT_SAVE_IMAGE_TO_DISK = true;

@Autowired
private DataManager dataManager;
Expand Down Expand Up @@ -126,7 +125,7 @@ public void getOneRecordExtentAsImage() throws Exception {
.andReturn().getResponse().getContentAsByteArray();

saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("0e7de4f5705ceb5a6c35f0d85d1fb4cd", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("f4a5b9c2c6b49db0f2f5bdbefd3736aa", DigestUtils.md5DigestAsHex(reponseBuffer));
}

@Test
Expand Down Expand Up @@ -156,7 +155,8 @@ public void lastModifiedModified() throws Exception {
.andExpect(content().contentType(API_PNG_EXPECTED_ENCODING))
.andReturn().getResponse().getContentAsByteArray();

assertEquals("0e7de4f5705ceb5a6c35f0d85d1fb4cd", DigestUtils.md5DigestAsHex(reponseBuffer));
saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("f4a5b9c2c6b49db0f2f5bdbefd3736aa", DigestUtils.md5DigestAsHex(reponseBuffer));
}

@Test
Expand All @@ -173,7 +173,7 @@ public void aggregatedWithTwoExtent() throws Exception {
.andReturn().getResponse().getContentAsByteArray();

saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("b381a4e5bde396b92ba4d798980f30fb", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("e8971ac1840c77b7bdc3cb026e921455", DigestUtils.md5DigestAsHex(reponseBuffer));
}

@Test
Expand All @@ -198,7 +198,7 @@ public void twoExtentFirstOneWithBothBoundingBoxAndPolygon() throws Exception {
.andReturn().getResponse().getContentAsByteArray();
saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName() + "-1");

assertEquals("1972e020a2955353b54035fb9328cebf", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("c4818d1c164fdcbcb66ac581780423c9", DigestUtils.md5DigestAsHex(reponseBuffer));

reponseBuffer = mockMvc.perform(get(String.format("/srv/api/records/%s/extents/2.png", uuid))
.session(mockHttpSession)
Expand All @@ -208,7 +208,7 @@ public void twoExtentFirstOneWithBothBoundingBoxAndPolygon() throws Exception {
.andReturn().getResponse().getContentAsByteArray();
saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName() + "-2");

assertEquals("aeab4dba0d59418fa8ec209cc6a4efa0", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("87416d6291ec1d19d0635d3bf17f10b4", DigestUtils.md5DigestAsHex(reponseBuffer));

reponseBuffer = mockMvc.perform(get(String.format("/srv/api/records/%s/extents/3.png", uuid))
.session(mockHttpSession)
Expand All @@ -218,7 +218,7 @@ public void twoExtentFirstOneWithBothBoundingBoxAndPolygon() throws Exception {
.andReturn().getResponse().getContentAsByteArray();
saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName() + "-3");

assertEquals("a5cd33942f207a48d230b636505318dc", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("323634b78d6bc2cba92912d78401e954", DigestUtils.md5DigestAsHex(reponseBuffer));
}


Expand All @@ -244,7 +244,7 @@ public void threeExtentThirdOne() throws Exception {
.andReturn().getResponse().getContentAsByteArray();

saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("df9dc5ff0300a3891d6af9ce42c1847f", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("827f96a4c37ef13f0dc2c33b92196afe", DigestUtils.md5DigestAsHex(reponseBuffer));
}

@Test
Expand All @@ -269,7 +269,7 @@ public void threeExtentThirdOne115_3() throws Exception {
.andReturn().getResponse().getContentAsByteArray();

saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("649e43578b03e4ff74d8c0d8272da1a9", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("64494e094033417a86dfd66304379d2c", DigestUtils.md5DigestAsHex(reponseBuffer));
}

@Test
Expand All @@ -286,7 +286,7 @@ public void threeExtentThirdOneIsABoundingBox() throws Exception {
.andReturn().getResponse().getContentAsByteArray();

saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("a5cd33942f207a48d230b636505318dc", DigestUtils.md5DigestAsHex(reponseBuffer));
assertEquals("323634b78d6bc2cba92912d78401e954", DigestUtils.md5DigestAsHex(reponseBuffer));
}

private String createTestData() throws Exception {
Expand Down Expand Up @@ -344,8 +344,9 @@ private String createMdFromXmlRessources(Element sampleMetadataXml) throws Excep
return uuid;
}

private void saveImageToDiskIfConfiguredToDoSo(byte[] reponseBuffer, String methodName) throws IOException {
if (DO_NOT_SAVE_IMAGE_TO_DISK) {
public static void saveImageToDiskIfConfiguredToDoSo(byte[] reponseBuffer, String methodName) throws IOException {
boolean SAVE_IMAGE_TO_DISK = false;
if (!SAVE_IMAGE_TO_DISK) {
return;
}
BufferedImage imag= ImageIO.read(new ByteArrayInputStream(reponseBuffer));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@
package org.fao.geonet.api.regions;

import org.fao.geonet.services.AbstractServiceIntegrationTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.WebApplicationContext;

import static org.fao.geonet.api.records.extent.MetadataExtentApiTest.saveImageToDiskIfConfiguredToDoSo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
Expand All @@ -44,11 +50,15 @@
*
* @author Jose García
*/
@ContextConfiguration(inheritLocations = true, locations = "classpath:extents-test-context.xml")
public class RegionsApiTest extends AbstractServiceIntegrationTest {

@Autowired
private WebApplicationContext wac;

@Rule
public TestName name = new TestName();

private MockMvc mockMvc;

private MockHttpSession mockHttpSession;
Expand Down Expand Up @@ -81,10 +91,41 @@ public void getRegionsForType() throws Exception {
.andExpect(status().isOk())
.andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
.andExpect(jsonPath("$.regions", hasSize(6)));
}


@Test
public void getMapOfMultiPoint() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
MockHttpSession mockHttpSession = loginAsAdmin();

byte[] reponseBuffer = mockMvc.perform(get("/srv/api/regions/geom.png?geomsrs=EPSG:4326&geom=GEOMETRYCOLLECTION(POINT(-32.277667%2037.291833),%20POINT(-44.949833%2023.367667),%20POINT(-31.556167%2037.841167))&width=600&strokeColor=255,255,0,255")
.session(mockHttpSession)
.accept(MediaType.IMAGE_PNG_VALUE))
.andExpect(status().is2xxSuccessful())
.andExpect(content().contentType(API_PNG_EXPECTED_ENCODING))
.andReturn().getResponse().getContentAsByteArray();

saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("3a817d8835c902dc86c65d833d9e4a55", DigestUtils.md5DigestAsHex(reponseBuffer));
}

@Test
public void getMapOfMultiBbox() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
MockHttpSession mockHttpSession = loginAsAdmin();

byte[] reponseBuffer = mockMvc.perform(get("/srv/api/regions/geom.png?geomsrs=EPSG:4326&geom=GEOMETRYCOLLECTION(POLYGON((10.5253%206.7632,10.5253%2023.2401,-18.3746%2023.2401,-18.3746%206.7632,10.5253%206.7632)),POLYGON((-28.8471%20-34.1253,-28.8471%205.2718,-73.9828%205.2718,-73.9828%20-34.1253,-28.8471%20-34.1253)))")
.session(mockHttpSession)
.accept(MediaType.IMAGE_PNG_VALUE))
.andExpect(status().is2xxSuccessful())
.andExpect(content().contentType(API_PNG_EXPECTED_ENCODING))
.andReturn().getResponse().getContentAsByteArray();

saveImageToDiskIfConfiguredToDoSo(reponseBuffer, name.getMethodName());
assertEquals("f82fa4618ad696a3561c0d4159bc5f1b", DigestUtils.md5DigestAsHex(reponseBuffer));
}

@Test
public void getRegionTypes() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,43 @@
else $key"/>
</xsl:function>

<xsl:function name="gn-fn-render:bboxes">
<xsl:param name="boundingBoxes" as="node()*"/>

<xsl:variable name="coordinates" as="node()*">
<xsl:for-each select="$boundingBoxes">
<coords east="{xs:double(*:eastBoundLongitude/*:Decimal)}"
south="{xs:double(*:southBoundLatitude/*:Decimal)}"
west="{xs:double(*:westBoundLongitude/*:Decimal)}"
north="{xs:double(*:northBoundLatitude/*:Decimal)}"/>
</xsl:for-each>
</xsl:variable>

<xsl:variable name="points"
select="$coordinates[@east = @west and @south = @north]"/>

<xsl:variable name="boxes"
select="$coordinates[@east != @west and @south != @north]"/>

<xsl:variable name="geometryCollection"
select="concat('GEOMETRYCOLLECTION(',
string-join($points/concat('POINT(', @east, '%20', @south, ')'), ','),
if (count($points) > 0 and count($boxes) > 0) then ',' else '',
string-join($boxes/concat('POLYGON((',
@east, '%20', @south, ',',
@east, '%20', @north, ',',
@west, '%20', @north, ',',
@west, '%20', @south, ',',
@east, '%20', @south, '))'), ','),
')')"/>
<xsl:variable name="numberFormat" select="'0.00'"/>

<div class="thumbnail extent">
<xsl:copy-of select="gn-fn-render:geometry($geometryCollection)"/>
</div>
</xsl:function>


<!-- Render coordinates of bbox and an images of the geometry
using the region API -->
<xsl:function name="gn-fn-render:bbox">
Expand Down

0 comments on commit 4ff5da2

Please sign in to comment.