diff --git a/backend/pom.xml b/backend/pom.xml
index c75573a..e4af898 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -37,6 +37,12 @@
spring-boot-starter-cache
+
+ org.bouncycastle
+ bcpkix-jdk18on
+ 1.78.1
+
+
org.springframework.boot
spring-boot-devtools
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/Application.java b/backend/src/main/java/com/rdkr/tide_display/backend/Application.java
index 3c693cd..5e93cf6 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/Application.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/Application.java
@@ -1,13 +1,17 @@
package com.rdkr.tide_display.backend;
+import java.security.Security;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
+ public static void main(String[] args) {
+ Security.addProvider(new BouncyCastleProvider());
+
+ SpringApplication.run(Application.class, args);
+ }
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/apple/Snapshot.java b/backend/src/main/java/com/rdkr/tide_display/backend/apple/Snapshot.java
new file mode 100644
index 0000000..6449bd7
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/apple/Snapshot.java
@@ -0,0 +1,9 @@
+package com.rdkr.tide_display.backend.apple;
+
+import java.net.URI;
+
+public interface Snapshot {
+
+ URI generateSnapshotURI(double latitude, double longitude);
+
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/apple/snapshot/SnapshotImpl.java b/backend/src/main/java/com/rdkr/tide_display/backend/apple/snapshot/SnapshotImpl.java
new file mode 100644
index 0000000..52ac97e
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/apple/snapshot/SnapshotImpl.java
@@ -0,0 +1,60 @@
+package com.rdkr.tide_display.backend.apple.snapshot;
+
+import com.rdkr.tide_display.backend.apple.Snapshot;
+import com.rdkr.tide_display.backend.crypto.Platforms;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.Signature;
+import java.util.Base64;
+import lombok.val;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@Component()
+public class SnapshotImpl implements Snapshot {
+
+ private final Platforms platforms;
+
+ @Value("${APPLE_TEAM_ID:dummy}")
+ private String teamId;
+
+ @Value("${APPLE_KEY_ID:dummy}")
+ private String keyId;
+
+ public SnapshotImpl(@Qualifier("AppleCrypto") Platforms platforms) {
+ this.platforms = platforms;
+ }
+
+ public URI generateSnapshotURI(double latitude, double longitude) {
+ val uriBuilder = UriComponentsBuilder.fromUriString("https://snapshot.apple-mapkit.com/api/v1/snapshot")
+ .queryParam("center", latitude + "," + longitude)
+ .queryParam("t", "standard")
+ .queryParam("scale", "1")
+ .queryParam("size", "540x540")
+ .queryParam("lang", "de-DE")
+ .queryParam("poi", "0")
+ .queryParam("teamId", teamId)
+ .queryParam("keyId", keyId);
+ return signRequest(uriBuilder);
+ }
+
+ private URI signRequest(UriComponentsBuilder uriBuilder) {
+ try {
+ val pk = platforms.loadPrivateKey("MapKit_D28AJ2A3UT.p8");
+ val signature = Signature.getInstance("SHA256withECDSA", "BC");
+ signature.initSign(pk);
+ signature.update(uriBuilder.toString().getBytes());
+ val signatureBytes = new String(signature.sign(), StandardCharsets.UTF_8);
+ val sign = Base64.getEncoder().encodeToString(signatureBytes.getBytes());
+ return uriBuilder
+ .queryParam("signature", sign)
+ .build()
+ .toUri();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/common/config/AppConfig.java b/backend/src/main/java/com/rdkr/tide_display/backend/common/config/AppConfig.java
new file mode 100644
index 0000000..b2a1688
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/common/config/AppConfig.java
@@ -0,0 +1,17 @@
+package com.rdkr.tide_display.backend.common.config;
+
+import com.rdkr.tide_display.backend.common.interceptor.RequestResponseLoggingInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClient;
+
+@Component
+public class AppConfig {
+
+ @Bean
+ public RestClient.Builder restClient() {
+ return RestClient
+ .builder()
+ .requestInterceptor(new RequestResponseLoggingInterceptor());
+ }
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/common/CacheConfig.java b/backend/src/main/java/com/rdkr/tide_display/backend/common/config/CacheConfig.java
similarity index 67%
rename from backend/src/main/java/com/rdkr/tide_display/backend/common/CacheConfig.java
rename to backend/src/main/java/com/rdkr/tide_display/backend/common/config/CacheConfig.java
index eef2526..a56c9c7 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/common/CacheConfig.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/common/config/CacheConfig.java
@@ -1,4 +1,4 @@
-package com.rdkr.tide_display.backend.common;
+package com.rdkr.tide_display.backend.common.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
@@ -12,6 +12,11 @@ public class CacheConfig {
@Bean
public CacheManager cacheManager() {
- return new ConcurrentMapCacheManager("map", "grayMap", "ePaper", "printHexValues");
+ return new ConcurrentMapCacheManager(
+ "privateKey",
+ "map",
+ "grayMap",
+ "ePaper",
+ "printHexValues");
}
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/common/interceptor/RequestResponseLoggingInterceptor.java b/backend/src/main/java/com/rdkr/tide_display/backend/common/interceptor/RequestResponseLoggingInterceptor.java
new file mode 100644
index 0000000..d6bc283
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/common/interceptor/RequestResponseLoggingInterceptor.java
@@ -0,0 +1,54 @@
+package com.rdkr.tide_display.backend.common.interceptor;
+
+import java.nio.charset.StandardCharsets;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.util.StreamUtils;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+@Slf4j
+public class RequestResponseLoggingInterceptor implements ClientHttpRequestInterceptor {
+
+ @Override
+ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
+ {
+ logRequest(request, body);
+ ClientHttpResponse response = execution.execute(request, body);
+ logResponse(response);
+
+ return response;
+ }
+
+ private void logRequest(HttpRequest request, byte[] body) throws IOException
+ {
+ if (log.isDebugEnabled())
+ {
+ log.debug("=========================== request begin ================================================");
+ log.debug("URI : {}", request.getURI());
+ log.debug("Method : {}", request.getMethod());
+ log.debug("Headers : {}", request.getHeaders());
+ //log.debug("Request body: {}", new String(body, StandardCharsets.UTF_8));
+ log.debug("=========================== request end ==================================================");
+ }
+ }
+
+ private void logResponse(ClientHttpResponse response) throws IOException
+ {
+ if (log.isDebugEnabled())
+ {
+ log.debug("=========================== response begin ===============================================");
+ log.debug("Status code : {}", response.getStatusCode());
+ log.debug("Status text : {}", response.getStatusText());
+ log.debug("Headers : {}", response.getHeaders());
+ //log.debug("Response body: {}", StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8));
+ log.debug("=========================== response end =================================================");
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/crypto/Platforms.java b/backend/src/main/java/com/rdkr/tide_display/backend/crypto/Platforms.java
new file mode 100644
index 0000000..9c06c7b
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/crypto/Platforms.java
@@ -0,0 +1,9 @@
+package com.rdkr.tide_display.backend.crypto;
+
+import java.security.PrivateKey;
+
+public interface Platforms {
+
+ PrivateKey loadPrivateKey(String filename);
+
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/crypto/platforms/AppleCrypto.java b/backend/src/main/java/com/rdkr/tide_display/backend/crypto/platforms/AppleCrypto.java
new file mode 100644
index 0000000..7418a5c
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/crypto/platforms/AppleCrypto.java
@@ -0,0 +1,31 @@
+package com.rdkr.tide_display.backend.crypto.platforms;
+
+import com.rdkr.tide_display.backend.crypto.Platforms;
+import java.io.FileReader;
+import java.security.PrivateKey;
+import lombok.val;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ResourceUtils;
+
+@Component("AppleCrypto")
+public class AppleCrypto implements Platforms {
+
+ @Cacheable(value = "privateKey", key = "#filename")
+ public PrivateKey loadPrivateKey(String filename) {
+ try {
+ val file = ResourceUtils.getFile("classpath:apple/" + filename );
+ val pemParser = new PEMParser(new FileReader(file));
+ val converter = new JcaPEMKeyConverter();
+ val object = (PrivateKeyInfo) pemParser.readObject();
+ val pKey = converter.getPrivateKey(object);
+ pemParser.close();
+ return pKey;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load private key", e);
+ }
+ }
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/crypto/platforms/GoogleCrypto.java b/backend/src/main/java/com/rdkr/tide_display/backend/crypto/platforms/GoogleCrypto.java
new file mode 100644
index 0000000..a7f9b5b
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/crypto/platforms/GoogleCrypto.java
@@ -0,0 +1,15 @@
+package com.rdkr.tide_display.backend.crypto.platforms;
+
+import com.rdkr.tide_display.backend.crypto.Platforms;
+import java.security.PrivateKey;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Component;
+
+@Component("GoogleCrypto")
+public class GoogleCrypto implements Platforms {
+
+ @Cacheable(value = "privateKey", key = "#filename")
+ public PrivateKey loadPrivateKey(String filename) {
+ return null;
+ }
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/RootController.java b/backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/RootController.java
index dd62293..10cce4d 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/RootController.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/RootController.java
@@ -8,8 +8,8 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/")
public class RootController {
- @GetMapping()
- public String index() {
- return "Tide Display Backend";
- }
+ @GetMapping()
+ public String index() {
+ return "Tide Display Backend";
+ }
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java
index 7f5375d..a324be1 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java
@@ -41,5 +41,5 @@ public class Firmware {
private String espIdf;
private String compiled;
-}
+ }
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java
index e42c7bb..8096c03 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java
@@ -5,9 +5,12 @@ import java.util.List;
import org.springframework.web.multipart.MultipartFile;
public interface StorageService {
+
List getFirmwareVersions();
byte[] download(String version, String filename);
String upload(MultipartFile file) throws IOException;
+
+ void save(String filename, byte[] data, String contentType, int expire);
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java
index ff4f4c6..7d55474 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java
@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
public class GoogleCloudBean {
@Bean
- public Storage getStorage(){
+ public Storage getStorage() {
return StorageOptions.getDefaultInstance().getService();
}
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java
index 5330063..18d8644 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java
@@ -8,9 +8,11 @@ import com.rdkr.tide_display.backend.gcp.StorageService;
import jakarta.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.val;
+import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@@ -34,7 +36,8 @@ public class StorageServiceImpl implements StorageService {
val result = new ArrayList();
val bucket = storage.list(bucketName, BlobListOption.currentDirectory(), BlobListOption.prefix("firmware/"));
for (val directory : bucket.iterateAll()) {
- val files = storage.list(bucketName, BlobListOption.currentDirectory(), BlobListOption.prefix(directory.getName()));
+ val files = storage.list(bucketName, BlobListOption.currentDirectory(),
+ BlobListOption.prefix(directory.getName()));
for (val file : files.iterateAll()) {
val name = file.getName();
if (name.endsWith(".bin")) {
@@ -53,7 +56,8 @@ public class StorageServiceImpl implements StorageService {
val meta = new Firmware.FirmwareMeta(projectName, flashSize, espIdf, compiled);
result.add(
- new Firmware(major, minor, patch, "/files/" + major + "." + minor + "." + patch + "/firmware.bin", file.getEtag(), file.getUpdateTimeOffsetDateTime(), meta));
+ new Firmware(major, minor, patch, "/files/" + major + "." + minor + "." + patch + "/firmware.bin",
+ file.getEtag(), file.getUpdateTimeOffsetDateTime(), meta));
}
}
}
@@ -84,7 +88,19 @@ public class StorageServiceImpl implements StorageService {
return null;
}
val version = extract(bytes, 48, 30);
- storage.create(BlobInfo.newBuilder(bucketName, "firmware/" + version + "/firmware.bin").build(), bytes);
+ save("firmware/" + version + "/firmware.bin", bytes, MediaType.APPLICATION_OCTET_STREAM_VALUE, 0);
return version;
}
+
+ @Override
+ public void save(String filename, byte[] data, String contentType, int expire) {
+ val metadata = new HashMap();
+ metadata.put("Cache-Control", "public, max-age=" + expire);
+ val blobInfo = BlobInfo
+ .newBuilder(bucketName, filename)
+ .setContentType(contentType)
+ .setMetadata(metadata)
+ .build();
+ storage.create(blobInfo, data);
+ }
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/controller/MapController.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/controller/MapController.java
index 6ef5efb..20ebfc7 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/images/controller/MapController.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/controller/MapController.java
@@ -4,6 +4,7 @@ import com.rdkr.tide_display.backend.images.domain.ImageFormat;
import com.rdkr.tide_display.backend.images.services.ImageConverterService;
import com.rdkr.tide_display.backend.images.services.MapService;
import java.io.IOException;
+import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
@@ -25,13 +26,14 @@ public class MapController {
@GetMapping
public ResponseEntity> getMap(
+ @RequestParam(required = false, defaultValue = "GoogleMaps") String mapProvider,
@RequestParam(required = false, defaultValue = "53.541962") double latitude,
@RequestParam(required = false, defaultValue = "9.993402") double longitude,
@RequestParam(required = false, defaultValue = "15") int zoom,
@RequestParam(required = false, defaultValue = "BINARY") ImageFormat format)
throws IOException {
- val map = mapService.getStaticMap(latitude, longitude, zoom);
- var result = converterService.grayscale(map);
+ val map = mapService.getStaticMap(mapProvider, latitude, longitude, zoom);
+ var result = converterService.convert(map);
val response = ResponseEntity.ok();
if (ImageFormat.PNG.equals(format)) {
return response.contentType(MediaType.IMAGE_PNG).body(result);
@@ -40,4 +42,9 @@ public class MapController {
return response.contentType(MediaType.APPLICATION_JSON).body(body);
}
}
+
+ @GetMapping("/providers")
+ public ResponseEntity> getMapProviders() {
+ return ResponseEntity.ok(mapService.getMapProviders());
+ }
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/ImageConverterService.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/ImageConverterService.java
index 6d9d730..ba900e5 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/ImageConverterService.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/ImageConverterService.java
@@ -16,41 +16,35 @@ import org.springframework.stereotype.Service;
@Slf4j
public class ImageConverterService {
- public byte[] grayscale(byte[] image) throws IOException {
- return grayscale_manual(image);
- }
-
- public byte[] grayscale_automatic(byte[] image) throws IOException {
+ public byte[] convert(byte[] image) throws IOException {
val img = ImageIO.read(new ByteArrayInputStream(image));
- val grayImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
- val g = grayImage.getGraphics();
- g.drawImage(img, 0, 0, null);
- g.dispose();
- val out = new ByteArrayOutputStream();
- ImageIO.write(grayImage, "png", out);
- return out.toByteArray();
- }
+ val celShadedImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
- public byte[] grayscale_manual(byte[] image) throws IOException {
- val img = ImageIO.read(new ByteArrayInputStream(image));
- val grayImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
- int rgb = 0, r = 0, g = 0, b = 0;
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {
- rgb = img.getRGB(x, y);
- r = (rgb >> 16) & 0xFF;
- g = (rgb >> 8) & 0xFF;
- b = (rgb & 0xFF);
- rgb = (int) (r * 0.299 + g * 0.587 + b * 0.114);
- rgb = (255 << 24) | (rgb << 16) | (rgb << 8) | rgb;
- grayImage.setRGB(x, y, rgb);
+ // Pixel-Farbe auslesen
+ int rgb = img.getRGB(x, y);
+
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ int gray = (r + g + b) / 3;
+
+ int celShadeLevel = gray / 16;
+ int newGray = celShadeLevel * 16;
+
+ int newRGB = (newGray << 16) | (newGray << 8) | newGray;
+
+ celShadedImage.setRGB(x, y, newRGB);
}
}
val out = new ByteArrayOutputStream();
- ImageIO.write(grayImage, "png", out);
+ ImageIO.write(celShadedImage, "png", out);
return out.toByteArray();
}
+
public ImageResponse ePaperFormat(byte[] image) throws IOException {
val img = ImageIO.read(new ByteArrayInputStream(image));
val width = img.getWidth();
@@ -74,11 +68,11 @@ public class ImageConverterService {
result.write(b);
}
}
- return new ImageResponse(width, height, Base64.getEncoder().encode(result.toByteArray()));
- }
-
- public void printHexValues(byte[] bytes) throws IOException {
- val hex = HexFormat.of().withUpperCase().withPrefix("0x").withSuffix(", ").formatHex(bytes);
- log.info("Hex value: {}", hex);
+ val bytes = result.toByteArray();
+ if (log.isDebugEnabled()) {
+ val hex = HexFormat.of().withUpperCase().withPrefix("0x").withSuffix(", ").formatHex(bytes);
+ log.debug("Image: {}", hex);
+ }
+ return new ImageResponse(width, height, Base64.getEncoder().encode(bytes));
}
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapService.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapService.java
index 765123a..004840e 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapService.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapService.java
@@ -1,7 +1,11 @@
package com.rdkr.tide_display.backend.images.services;
+import java.util.List;
+
public interface MapService {
- byte[] getStaticMap(double latitude, double longitude, int zoom);
+ List getMapProviders();
+
+ byte[] getStaticMap(String provider, double latitude, double longitude, int zoom);
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapServiceImpl.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapServiceImpl.java
index c33dcc0..bbb9956 100644
--- a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapServiceImpl.java
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/MapServiceImpl.java
@@ -1,25 +1,47 @@
package com.rdkr.tide_display.backend.images.services;
+import com.rdkr.tide_display.backend.gcp.StorageService;
+import com.rdkr.tide_display.backend.images.services.mapProvider.MapProvider;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.cache.annotation.Cacheable;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestClient;
+import org.springframework.util.MimeType;
@Service
public class MapServiceImpl implements MapService {
- @Cacheable("map")
- public byte[] getStaticMap(double latitude, double longitude, int zoom) {
- val client = RestClient.create("https://maps.googleapis.com/maps/api/staticmap");
- return client.get()
- .uri(uriBuilder -> uriBuilder
- .queryParam("center", latitude + "," + longitude)
- .queryParam("zoom", "" + zoom)
- .queryParam("size", "540x540")
- .queryParam("map_id", "2f371c2346218fe8")
- .queryParam("key", "AIzaSyARgP_FKFXsrcgVd_HVWoIfH5N8-a88wlQ")
- .build())
- .retrieve()
- .body(byte[].class);
+ private final Map providers = new HashMap<>();
+ private final StorageService storageService;
+
+ public MapServiceImpl(List mapProviders, StorageService storageService) {
+ this.storageService = storageService;
+ mapProviders.forEach(
+ provider -> providers.put(provider.getClass().getAnnotation(Component.class).value().toUpperCase(), provider));
+ }
+
+ public List getMapProviders() {
+ return List.copyOf(providers.keySet());
+ }
+
+ @Cacheable(value = "map", key = "{#provider, #latitude, #longitude, #zoom}")
+ public byte[] getStaticMap(String provider, double latitude, double longitude, int zoom) {
+ val mapProvider = providers.get(provider.toUpperCase());
+ if (mapProvider == null) {
+ throw new UnsupportedOperationException("Map provider not found: " + provider);
+ }
+ val imageData = mapProvider.getStaticMap(latitude, longitude, zoom);
+ cacheImage(provider, latitude, longitude, zoom, imageData);
+ return imageData;
+ }
+
+ private void cacheImage(String provider, double latitude, double longitude, int zoom, byte[] image) {
+ storageService.save("mapProvider/" + provider.toUpperCase() + "/" + latitude + "/" + longitude + "/" + zoom + "/image.png", image, MediaType.IMAGE_PNG_VALUE, 60);
}
}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/AppleMapKit.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/AppleMapKit.java
new file mode 100644
index 0000000..bcb3d94
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/AppleMapKit.java
@@ -0,0 +1,29 @@
+package com.rdkr.tide_display.backend.images.services.mapProvider;
+
+import com.rdkr.tide_display.backend.apple.Snapshot;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClient;
+
+@Component("AppleMapKit")
+@RequiredArgsConstructor
+public class AppleMapKit implements MapProvider {
+
+ private final RestClient.Builder restClient;
+ private final Snapshot snapshot;
+
+ @Override
+ public byte[] getStaticMap(double latitude, double longitude, int zoom) {
+ try {
+ val uri = snapshot.generateSnapshotURI(latitude, longitude);
+ val client = restClient.baseUrl(uri.toString()).build();
+ return client.get()
+ .retrieve()
+ .body(byte[].class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/GoogleMaps.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/GoogleMaps.java
new file mode 100644
index 0000000..199ed32
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/GoogleMaps.java
@@ -0,0 +1,36 @@
+package com.rdkr.tide_display.backend.images.services.mapProvider;
+
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClient;
+
+@Component("GoogleMaps")
+@RequiredArgsConstructor
+public class GoogleMaps implements MapProvider {
+
+ private final RestClient.Builder restClient;
+
+ @Value("${GCP_MAP_ID:dummy}")
+ private String mapId;
+
+ @Value("${GCP_API_KEY:dummy}")
+ private String apiKey;
+
+ @Override
+ public byte[] getStaticMap(double latitude, double longitude, int zoom) {
+ val client = restClient.baseUrl("https://maps.googleapis.com/maps/api/staticmap").build();
+ return client.get()
+ .uri(builder -> builder
+ .queryParam("center", latitude + "," + longitude)
+ .queryParam("zoom", "" + zoom)
+ .queryParam("size", "540x540")
+ .queryParam("map_id", mapId)
+ .queryParam("key", apiKey)
+ .build())
+ .retrieve()
+ .body(byte[].class);
+ }
+
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/MapBox.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/MapBox.java
new file mode 100644
index 0000000..a501156
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/MapBox.java
@@ -0,0 +1,31 @@
+package com.rdkr.tide_display.backend.images.services.mapProvider;
+
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClient;
+
+@Component("MapBox")
+@RequiredArgsConstructor
+public class MapBox implements MapProvider {
+
+ private final RestClient.Builder restClient;
+
+ @Value("${MAPBOX_TOKEN:dummy}")
+ private String accessToken;
+
+ @Override
+ public byte[] getStaticMap(double latitude, double longitude, int zoom) {
+ val client = restClient.baseUrl("https://api.mapbox.com/styles/v1/mapbox/streets-v12/static").build();
+ return client.get()
+ .uri(builder -> builder
+ .path("/" + longitude + "," + latitude + "," + (zoom - 1))
+ .path("/540x540")
+ .queryParam("access_token", accessToken)
+ .build())
+ .retrieve()
+ .body(byte[].class);
+ }
+
+}
diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/MapProvider.java b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/MapProvider.java
new file mode 100644
index 0000000..b63c7f1
--- /dev/null
+++ b/backend/src/main/java/com/rdkr/tide_display/backend/images/services/mapProvider/MapProvider.java
@@ -0,0 +1,7 @@
+package com.rdkr.tide_display.backend.images.services.mapProvider;
+
+public interface MapProvider {
+
+ byte[] getStaticMap(double latitude, double longitude, int zoom);
+
+}
diff --git a/backend/src/main/resources/application-dev.yaml b/backend/src/main/resources/application-dev.yaml
new file mode 100644
index 0000000..2368f1e
--- /dev/null
+++ b/backend/src/main/resources/application-dev.yaml
@@ -0,0 +1,3 @@
+logging:
+ level:
+ com.rdkr: trace