Skip to content

Commit a64baf1

Browse files
committed
feat: support bbox in COG map export
1 parent f802d87 commit a64baf1

6 files changed

Lines changed: 214 additions & 21 deletions

File tree

core/src/main/java/org/mapfish/print/output/MapCogExportOutputFormat.java

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.geotools.coverage.grid.io.imageio.GeoToolsWriteParams;
2727
import org.geotools.gce.geotiff.GeoTiffFormat;
2828
import org.geotools.gce.geotiff.GeoTiffWriteParams;
29+
import org.geotools.geometry.jts.ReferencedEnvelope;
2930
import org.geotools.referencing.CRS;
3031
import org.geotools.referencing.operation.transform.AffineTransform2D;
3132
import org.mapfish.print.Constants;
@@ -98,30 +99,50 @@ public final Processor.ExecutionContext print(
9899
: Path.of(mapSubReport);
99100

100101
BufferedImage image = ImageIO.read(path.toFile());
101-
PJsonArray center = mapJson.getJSONArray("center");
102102

103-
double centerX = center.getDouble(0);
104-
double centerY = center.getDouble(1);
105-
double cx = image.getWidth() / 2.0;
106-
double cy = image.getHeight() / 2.0;
107-
108-
double scale = mapJson.getDouble("scale");
109-
double dpi = mapJson.getDouble("dpi");
110-
double metersPerPixel = scale * METERS_PER_INCH / dpi;
111103
String srs = mapJson.getString("projection");
112-
double rotation = mapJson.getDouble("rotation");
113-
114-
AffineTransform gridToCRS = new AffineTransform();
115-
gridToCRS.translate(centerX, centerY);
116-
gridToCRS.rotate(Math.toRadians(rotation));
117-
gridToCRS.scale(metersPerPixel, -metersPerPixel);
118-
gridToCRS.translate(-cx, -cy);
119-
120104
CoordinateReferenceSystem crs = CRS.decode(srs);
121-
MathTransform mathTransform = new AffineTransform2D(gridToCRS);
122105
GridCoverageFactory factory = new GridCoverageFactory();
123-
GridCoverage2D coverage =
124-
factory.create("coverage", image, crs, mathTransform, null, null, null);
106+
GridCoverage2D coverage;
107+
108+
if (mapJson.has("center")) {
109+
PJsonArray center = mapJson.getJSONArray("center");
110+
111+
double centerX = center.getDouble(0);
112+
double centerY = center.getDouble(1);
113+
double cx = image.getWidth() / 2.0;
114+
double cy = image.getHeight() / 2.0;
115+
116+
double scale = mapJson.getDouble("scale");
117+
double dpi = mapJson.getDouble("dpi");
118+
double metersPerPixel = scale * METERS_PER_INCH / dpi;
119+
120+
double rotation = mapJson.has("rotation") ? mapJson.getDouble("rotation") : 0.0;
121+
122+
AffineTransform gridToCRS = new AffineTransform();
123+
gridToCRS.translate(centerX, centerY);
124+
gridToCRS.rotate(Math.toRadians(rotation));
125+
gridToCRS.scale(metersPerPixel, -metersPerPixel);
126+
gridToCRS.translate(-cx, -cy);
127+
128+
MathTransform mathTransform = new AffineTransform2D(gridToCRS);
129+
130+
coverage = factory.create("coverage", image, crs, mathTransform, null, null, null);
131+
} else if (mapJson.has("bbox")) {
132+
PJsonArray bbox = mapJson.getJSONArray("bbox");
133+
134+
double minX = bbox.getDouble(0);
135+
double minY = bbox.getDouble(1);
136+
double maxX = bbox.getDouble(2);
137+
double maxY = bbox.getDouble(3);
138+
139+
ReferencedEnvelope envelope = new ReferencedEnvelope(minX, maxX, minY, maxY, crs);
140+
141+
coverage = factory.create("coverage", image, envelope);
142+
143+
} else {
144+
throw new IOException("COG export requires either center + scale or bbox");
145+
}
125146

126147
final int tileWidth = 512;
127148
final int tileHeight = 512;

core/src/test/java/org/mapfish/print/output/JasperReportOutputFormatSimpleMapTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public void testAllOutputFormats() throws Exception {
6161
for (OutputFormat format : this.outputFormat.values()) {
6262
if ("bmp".equals(format.getFileSuffix())
6363
|| "jpeg".equals(format.getFileSuffix())
64-
|| "jpg".equals(format.getFileSuffix())) {
64+
|| "jpg".equals(format.getFileSuffix())
65+
|| format instanceof MapCogExportOutputFormat) {
6566
// BMP and JPEG do not support transparency
6667
continue;
6768
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.mapfish.print.output;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.io.ByteArrayInputStream;
8+
import java.io.ByteArrayOutputStream;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import org.geotools.api.coverage.grid.GridCoverageReader;
12+
import org.geotools.coverage.grid.GridCoverage2D;
13+
import org.geotools.gce.geotiff.GeoTiffFormat;
14+
import org.junit.jupiter.api.Test;
15+
import org.mapfish.print.AbstractMapfishSpringTest;
16+
import org.mapfish.print.config.Configuration;
17+
import org.mapfish.print.config.ConfigurationFactory;
18+
import org.mapfish.print.wrapper.json.PJsonObject;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
21+
public class MapCogExportOutputFormatTest extends AbstractMapfishSpringTest {
22+
23+
private static final String BASE_DIR = "map_cog/";
24+
25+
@Autowired private ConfigurationFactory configurationFactory;
26+
27+
@Autowired private Map<String, OutputFormat> outputFormat;
28+
29+
@Test
30+
public void testCogExport() throws Exception {
31+
final Configuration config = configurationFactory.getConfig(getFile(BASE_DIR + "config.yaml"));
32+
33+
final PJsonObject requestData =
34+
parseJSONObjectFromFile(MapCogExportOutputFormatTest.class, BASE_DIR + "requestData.json");
35+
36+
final OutputFormat format = this.outputFormat.get("cogMapOutputFormat");
37+
38+
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
39+
40+
format.print(
41+
new HashMap<>(),
42+
requestData,
43+
config,
44+
getFile(MapCogExportOutputFormatTest.class, BASE_DIR),
45+
getTaskDirectory(),
46+
outputStream);
47+
48+
final byte[] result = outputStream.toByteArray();
49+
50+
assertTrue(result.length > 0);
51+
52+
final GeoTiffFormat geoTiffFormat = new GeoTiffFormat();
53+
final GridCoverageReader reader = geoTiffFormat.getReader(new ByteArrayInputStream(result));
54+
55+
assertNotNull(reader);
56+
57+
try {
58+
final GridCoverage2D coverage = (GridCoverage2D) reader.read(null);
59+
60+
assertNotNull(coverage);
61+
assertNotNull(coverage.getCoordinateReferenceSystem());
62+
63+
final var envelope = coverage.getEnvelope2D();
64+
65+
assertEquals(97.5, envelope.getMinX(), 0.000001);
66+
assertEquals(-0.5, envelope.getMinY(), 0.000001);
67+
assertEquals(107.5, envelope.getMaxX(), 0.000001);
68+
assertEquals(1.5, envelope.getMaxY(), 0.000001);
69+
70+
} finally {
71+
reader.dispose();
72+
}
73+
}
74+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
throwErrorOnExtraParameters: true
2+
3+
templates:
4+
main: !template
5+
mapExport: true
6+
attributes:
7+
map: !map
8+
width: 500
9+
height: 100
10+
maxDpi: 400
11+
zoomSnapTolerance: 0.025
12+
zoomLevelSnapStrategy: CLOSEST_LOWER_SCALE_ON_TIE
13+
zoomLevels: !zoomLevels
14+
scales: [50000, 100000, 500000, 1000000]
15+
processors:
16+
- !createMap {}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [
4+
{
5+
"type": "Feature",
6+
"geometry": {
7+
"type": "Polygon",
8+
"coordinates": [
9+
[
10+
[102.0, 0.0],
11+
[103.0, 0.0],
12+
[103.0, 1.0],
13+
[102.0, 1.0],
14+
[102.0, 0.0]
15+
]
16+
]
17+
},
18+
"properties": {
19+
"prop0": "value1",
20+
"prop1": 1.0
21+
}
22+
},
23+
{
24+
"type": "Feature",
25+
"geometry": {
26+
"type": "Polygon",
27+
"coordinates": [
28+
[
29+
[104.0, 0.0],
30+
[105.0, 0.0],
31+
[105.0, 1.0],
32+
[104.0, 1.0],
33+
[104.0, 0.0]
34+
]
35+
]
36+
},
37+
"properties": {
38+
"prop0": "value0",
39+
"prop1": 0.0
40+
}
41+
},
42+
{
43+
"type": "Feature",
44+
"geometry": {
45+
"type": "Polygon",
46+
"coordinates": [
47+
[
48+
[100.0, 0.0],
49+
[101.0, 0.0],
50+
[101.0, 1.0],
51+
[100.0, 1.0],
52+
[100.0, 0.0]
53+
]
54+
]
55+
},
56+
"properties": {
57+
"prop0": "value0",
58+
"prop1": { "this": "that" }
59+
}
60+
}
61+
]
62+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"layout": "main",
3+
"outputFormat": "cog",
4+
"attributes": {
5+
"map": {
6+
"bbox": [97.5, -0.5, 107.5, 1.5],
7+
"projection": "CRS:84",
8+
"dpi": 72,
9+
"layers": [
10+
{
11+
"type": "geojson",
12+
"style": "polygon",
13+
"renderAsSvg": false,
14+
"geoJson": @@importFile(geojson.json)@@
15+
}
16+
]
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)