|
11 | 11 | import com.fasterxml.jackson.databind.node.ArrayNode; |
12 | 12 | import com.fasterxml.jackson.databind.node.NullNode; |
13 | 13 | import com.fasterxml.jackson.databind.node.ObjectNode; |
| 14 | +import org.springframework.core.io.ClassPathResource; |
14 | 15 | import org.springframework.http.ContentDisposition; |
15 | 16 | import org.springframework.http.HttpHeaders; |
16 | 17 | import org.springframework.http.MediaType; |
17 | 18 | import org.springframework.http.ResponseEntity; |
| 19 | +import org.springframework.util.StringUtils; |
18 | 20 | import org.springframework.web.bind.annotation.*; |
19 | 21 | import org.springframework.web.multipart.MultipartFile; |
20 | 22 |
|
|
24 | 26 | import java.awt.image.BufferedImage; |
25 | 27 | import java.io.ByteArrayInputStream; |
26 | 28 | import java.io.ByteArrayOutputStream; |
| 29 | +import java.io.IOException; |
| 30 | +import java.io.InputStream; |
27 | 31 | import java.nio.charset.StandardCharsets; |
28 | 32 | import java.time.Instant; |
29 | 33 | import java.util.Base64; |
30 | 34 | import java.util.List; |
| 35 | +import java.util.Locale; |
31 | 36 | import java.util.Map; |
32 | 37 | import java.util.zip.ZipEntry; |
33 | 38 | import java.util.zip.ZipOutputStream; |
@@ -99,6 +104,9 @@ public ResponseEntity<?> exportInspection(@PathVariable String id) { |
99 | 104 | } else { |
100 | 105 | inspectionNode.putNull("transformer"); |
101 | 106 | } |
| 107 | + BaselineSelection baselineSelection = resolveBaselineSelection(inspection); |
| 108 | + putNullable(inspectionNode, "baselineWeather", |
| 109 | + baselineSelection != null ? baselineSelection.weatherLabel() : null); |
102 | 110 |
|
103 | 111 | JsonNode currentBoxes = parseJsonNode(mapper, inspection.getBoundingBoxes()); |
104 | 112 | JsonNode currentFaults = parseJsonNode(mapper, inspection.getFaultTypes()); |
@@ -185,6 +193,26 @@ public ResponseEntity<?> exportInspection(@PathVariable String id) { |
185 | 193 | zos.write(imageBytes); |
186 | 194 | zos.closeEntry(); |
187 | 195 | } |
| 196 | + |
| 197 | + if (baselineSelection != null) { |
| 198 | + byte[] baselineBytes = decodeDataUrl(baselineSelection.dataUrl()); |
| 199 | + if (baselineBytes != null && baselineBytes.length > 0) { |
| 200 | + String baselineExt = guessImageExtension(baselineSelection.dataUrl()); |
| 201 | + String weatherLabel = baselineSelection.weatherLabel() != null |
| 202 | + ? sanitizeFilename(baselineSelection.weatherLabel()) |
| 203 | + : "baseline"; |
| 204 | + if (weatherLabel.isBlank()) { |
| 205 | + weatherLabel = "baseline"; |
| 206 | + } |
| 207 | + ZipEntry baselineEntry = new ZipEntry("baseline-" + weatherLabel + "." + baselineExt); |
| 208 | + zos.putNextEntry(baselineEntry); |
| 209 | + zos.write(baselineBytes); |
| 210 | + zos.closeEntry(); |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + writeResourceToZip(zos, "export/plot_bounding_boxes.py", "tools/plot_bounding_boxes.py"); |
| 215 | + writeResourceToZip(zos, "export/README.md", "tools/README.md"); |
188 | 216 | } |
189 | 217 |
|
190 | 218 | byte[] zipBytes = baos.toByteArray(); |
@@ -674,6 +702,71 @@ private static String sanitizeFilename(String value) { |
674 | 702 | return sanitized.isBlank() ? "inspection" : sanitized; |
675 | 703 | } |
676 | 704 |
|
| 705 | + private static BaselineSelection resolveBaselineSelection(Inspection inspection) { |
| 706 | + if (inspection == null) { |
| 707 | + return null; |
| 708 | + } |
| 709 | + Transformer transformer = inspection.getTransformer(); |
| 710 | + if (transformer == null) { |
| 711 | + return null; |
| 712 | + } |
| 713 | + String preferredWeather = determinePreferredWeather(inspection); |
| 714 | + if (StringUtils.hasText(preferredWeather)) { |
| 715 | + String candidate = lookupBaselineForWeather(transformer, preferredWeather); |
| 716 | + if (StringUtils.hasText(candidate)) { |
| 717 | + return new BaselineSelection(candidate, preferredWeather.toLowerCase(Locale.ROOT)); |
| 718 | + } |
| 719 | + } |
| 720 | + if (StringUtils.hasText(transformer.getSunnyImage())) { |
| 721 | + return new BaselineSelection(transformer.getSunnyImage(), "sunny"); |
| 722 | + } |
| 723 | + if (StringUtils.hasText(transformer.getCloudyImage())) { |
| 724 | + return new BaselineSelection(transformer.getCloudyImage(), "cloudy"); |
| 725 | + } |
| 726 | + if (StringUtils.hasText(transformer.getWindyImage())) { |
| 727 | + return new BaselineSelection(transformer.getWindyImage(), "windy"); |
| 728 | + } |
| 729 | + return null; |
| 730 | + } |
| 731 | + |
| 732 | + private static String determinePreferredWeather(Inspection inspection) { |
| 733 | + String weather = inspection.getLastAnalysisWeather(); |
| 734 | + if (!StringUtils.hasText(weather)) { |
| 735 | + weather = inspection.getWeather(); |
| 736 | + } |
| 737 | + if (!StringUtils.hasText(weather)) { |
| 738 | + return null; |
| 739 | + } |
| 740 | + return weather.trim().toLowerCase(Locale.ROOT); |
| 741 | + } |
| 742 | + |
| 743 | + private static String lookupBaselineForWeather(Transformer transformer, String weather) { |
| 744 | + if (!StringUtils.hasText(weather) || transformer == null) { |
| 745 | + return null; |
| 746 | + } |
| 747 | + return switch (weather.toLowerCase(Locale.ROOT)) { |
| 748 | + case "sunny" -> transformer.getSunnyImage(); |
| 749 | + case "cloudy" -> transformer.getCloudyImage(); |
| 750 | + case "rainy", "windy" -> transformer.getWindyImage(); |
| 751 | + default -> null; |
| 752 | + }; |
| 753 | + } |
| 754 | + |
| 755 | + private static void writeResourceToZip(ZipOutputStream zos, String resourcePath, String entryName) throws IOException { |
| 756 | + ClassPathResource resource = new ClassPathResource(resourcePath); |
| 757 | + if (!resource.exists()) { |
| 758 | + return; |
| 759 | + } |
| 760 | + ZipEntry entry = new ZipEntry(entryName); |
| 761 | + zos.putNextEntry(entry); |
| 762 | + try (InputStream is = resource.getInputStream()) { |
| 763 | + is.transferTo(zos); |
| 764 | + } |
| 765 | + zos.closeEntry(); |
| 766 | + } |
| 767 | + |
| 768 | + private record BaselineSelection(String dataUrl, String weatherLabel) {} |
| 769 | + |
677 | 770 | @PostMapping("/{id}/clear-analysis") |
678 | 771 | public ResponseEntity<?> clearAnalysis(@PathVariable String id) { |
679 | 772 | return repo.findById(id).map(i -> { |
|
0 commit comments