generate binary data of map picture
Signed-off-by: Peter Siegmund <peter@rdkr.com>
This commit is contained in:
@@ -37,6 +37,12 @@
|
|||||||
<artifactId>spring-boot-starter-cache</artifactId>
|
<artifactId>spring-boot-starter-cache</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk18on</artifactId>
|
||||||
|
<version>1.78.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-devtools</artifactId>
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package com.rdkr.tide_display.backend;
|
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.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@@ -7,6 +9,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
public class Application {
|
public class Application {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
|
||||||
SpringApplication.run(Application.class, args);
|
SpringApplication.run(Application.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.rdkr.tide_display.backend.apple;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
public interface Snapshot {
|
||||||
|
|
||||||
|
URI generateSnapshotURI(double latitude, double longitude);
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
@@ -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.CacheManager;
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
@@ -12,6 +12,11 @@ public class CacheConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CacheManager cacheManager() {
|
public CacheManager cacheManager() {
|
||||||
return new ConcurrentMapCacheManager("map", "grayMap", "ePaper", "printHexValues");
|
return new ConcurrentMapCacheManager(
|
||||||
|
"privateKey",
|
||||||
|
"map",
|
||||||
|
"grayMap",
|
||||||
|
"ePaper",
|
||||||
|
"printHexValues");
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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 =================================================");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.rdkr.tide_display.backend.crypto;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
|
public interface Platforms {
|
||||||
|
|
||||||
|
PrivateKey loadPrivateKey(String filename);
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -41,5 +41,5 @@ public class Firmware {
|
|||||||
private String espIdf;
|
private String espIdf;
|
||||||
|
|
||||||
private String compiled;
|
private String compiled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,12 @@ import java.util.List;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
public interface StorageService {
|
public interface StorageService {
|
||||||
|
|
||||||
List<Firmware> getFirmwareVersions();
|
List<Firmware> getFirmwareVersions();
|
||||||
|
|
||||||
byte[] download(String version, String filename);
|
byte[] download(String version, String filename);
|
||||||
|
|
||||||
String upload(MultipartFile file) throws IOException;
|
String upload(MultipartFile file) throws IOException;
|
||||||
|
|
||||||
|
void save(String filename, byte[] data, String contentType, int expire);
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
|
|||||||
public class GoogleCloudBean {
|
public class GoogleCloudBean {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Storage getStorage(){
|
public Storage getStorage() {
|
||||||
return StorageOptions.getDefaultInstance().getService();
|
return StorageOptions.getDefaultInstance().getService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,9 +8,11 @@ import com.rdkr.tide_display.backend.gcp.StorageService;
|
|||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@@ -34,7 +36,8 @@ public class StorageServiceImpl implements StorageService {
|
|||||||
val result = new ArrayList<Firmware>();
|
val result = new ArrayList<Firmware>();
|
||||||
val bucket = storage.list(bucketName, BlobListOption.currentDirectory(), BlobListOption.prefix("firmware/"));
|
val bucket = storage.list(bucketName, BlobListOption.currentDirectory(), BlobListOption.prefix("firmware/"));
|
||||||
for (val directory : bucket.iterateAll()) {
|
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()) {
|
for (val file : files.iterateAll()) {
|
||||||
val name = file.getName();
|
val name = file.getName();
|
||||||
if (name.endsWith(".bin")) {
|
if (name.endsWith(".bin")) {
|
||||||
@@ -53,7 +56,8 @@ public class StorageServiceImpl implements StorageService {
|
|||||||
val meta = new Firmware.FirmwareMeta(projectName, flashSize, espIdf, compiled);
|
val meta = new Firmware.FirmwareMeta(projectName, flashSize, espIdf, compiled);
|
||||||
|
|
||||||
result.add(
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
val version = extract(bytes, 48, 30);
|
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;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(String filename, byte[] data, String contentType, int expire) {
|
||||||
|
val metadata = new HashMap<String, String>();
|
||||||
|
metadata.put("Cache-Control", "public, max-age=" + expire);
|
||||||
|
val blobInfo = BlobInfo
|
||||||
|
.newBuilder(bucketName, filename)
|
||||||
|
.setContentType(contentType)
|
||||||
|
.setMetadata(metadata)
|
||||||
|
.build();
|
||||||
|
storage.create(blobInfo, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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.ImageConverterService;
|
||||||
import com.rdkr.tide_display.backend.images.services.MapService;
|
import com.rdkr.tide_display.backend.images.services.MapService;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
@@ -25,13 +26,14 @@ public class MapController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<?> getMap(
|
public ResponseEntity<?> getMap(
|
||||||
|
@RequestParam(required = false, defaultValue = "GoogleMaps") String mapProvider,
|
||||||
@RequestParam(required = false, defaultValue = "53.541962") double latitude,
|
@RequestParam(required = false, defaultValue = "53.541962") double latitude,
|
||||||
@RequestParam(required = false, defaultValue = "9.993402") double longitude,
|
@RequestParam(required = false, defaultValue = "9.993402") double longitude,
|
||||||
@RequestParam(required = false, defaultValue = "15") int zoom,
|
@RequestParam(required = false, defaultValue = "15") int zoom,
|
||||||
@RequestParam(required = false, defaultValue = "BINARY") ImageFormat format)
|
@RequestParam(required = false, defaultValue = "BINARY") ImageFormat format)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
val map = mapService.getStaticMap(latitude, longitude, zoom);
|
val map = mapService.getStaticMap(mapProvider, latitude, longitude, zoom);
|
||||||
var result = converterService.grayscale(map);
|
var result = converterService.convert(map);
|
||||||
val response = ResponseEntity.ok();
|
val response = ResponseEntity.ok();
|
||||||
if (ImageFormat.PNG.equals(format)) {
|
if (ImageFormat.PNG.equals(format)) {
|
||||||
return response.contentType(MediaType.IMAGE_PNG).body(result);
|
return response.contentType(MediaType.IMAGE_PNG).body(result);
|
||||||
@@ -40,4 +42,9 @@ public class MapController {
|
|||||||
return response.contentType(MediaType.APPLICATION_JSON).body(body);
|
return response.contentType(MediaType.APPLICATION_JSON).body(body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/providers")
|
||||||
|
public ResponseEntity<List<String>> getMapProviders() {
|
||||||
|
return ResponseEntity.ok(mapService.getMapProviders());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,41 +16,35 @@ import org.springframework.stereotype.Service;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ImageConverterService {
|
public class ImageConverterService {
|
||||||
|
|
||||||
public byte[] grayscale(byte[] image) throws IOException {
|
public byte[] convert(byte[] image) throws IOException {
|
||||||
return grayscale_manual(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] grayscale_automatic(byte[] image) throws IOException {
|
|
||||||
val img = ImageIO.read(new ByteArrayInputStream(image));
|
val img = ImageIO.read(new ByteArrayInputStream(image));
|
||||||
val grayImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
|
val celShadedImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
val g = grayImage.getGraphics();
|
|
||||||
g.drawImage(img, 0, 0, null);
|
|
||||||
g.dispose();
|
|
||||||
val out = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(grayImage, "png", out);
|
|
||||||
return out.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 y = 0; y < img.getHeight(); y++) {
|
||||||
for (int x = 0; x < img.getWidth(); x++) {
|
for (int x = 0; x < img.getWidth(); x++) {
|
||||||
rgb = img.getRGB(x, y);
|
// Pixel-Farbe auslesen
|
||||||
r = (rgb >> 16) & 0xFF;
|
int rgb = img.getRGB(x, y);
|
||||||
g = (rgb >> 8) & 0xFF;
|
|
||||||
b = (rgb & 0xFF);
|
int r = (rgb >> 16) & 0xFF;
|
||||||
rgb = (int) (r * 0.299 + g * 0.587 + b * 0.114);
|
int g = (rgb >> 8) & 0xFF;
|
||||||
rgb = (255 << 24) | (rgb << 16) | (rgb << 8) | rgb;
|
int b = rgb & 0xFF;
|
||||||
grayImage.setRGB(x, y, rgb);
|
|
||||||
|
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();
|
val out = new ByteArrayOutputStream();
|
||||||
ImageIO.write(grayImage, "png", out);
|
ImageIO.write(celShadedImage, "png", out);
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ImageResponse ePaperFormat(byte[] image) throws IOException {
|
public ImageResponse ePaperFormat(byte[] image) throws IOException {
|
||||||
val img = ImageIO.read(new ByteArrayInputStream(image));
|
val img = ImageIO.read(new ByteArrayInputStream(image));
|
||||||
val width = img.getWidth();
|
val width = img.getWidth();
|
||||||
@@ -74,11 +68,11 @@ public class ImageConverterService {
|
|||||||
result.write(b);
|
result.write(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ImageResponse(width, height, Base64.getEncoder().encode(result.toByteArray()));
|
val bytes = result.toByteArray();
|
||||||
}
|
if (log.isDebugEnabled()) {
|
||||||
|
|
||||||
public void printHexValues(byte[] bytes) throws IOException {
|
|
||||||
val hex = HexFormat.of().withUpperCase().withPrefix("0x").withSuffix(", ").formatHex(bytes);
|
val hex = HexFormat.of().withUpperCase().withPrefix("0x").withSuffix(", ").formatHex(bytes);
|
||||||
log.info("Hex value: {}", hex);
|
log.debug("Image: {}", hex);
|
||||||
|
}
|
||||||
|
return new ImageResponse(width, height, Base64.getEncoder().encode(bytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
package com.rdkr.tide_display.backend.images.services;
|
package com.rdkr.tide_display.backend.images.services;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface MapService {
|
public interface MapService {
|
||||||
|
|
||||||
byte[] getStaticMap(double latitude, double longitude, int zoom);
|
List<String> getMapProviders();
|
||||||
|
|
||||||
|
byte[] getStaticMap(String provider, double latitude, double longitude, int zoom);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,25 +1,47 @@
|
|||||||
package com.rdkr.tide_display.backend.images.services;
|
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 lombok.val;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
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.stereotype.Service;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.util.MimeType;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class MapServiceImpl implements MapService {
|
public class MapServiceImpl implements MapService {
|
||||||
|
|
||||||
@Cacheable("map")
|
private final Map<String, MapProvider> providers = new HashMap<>();
|
||||||
public byte[] getStaticMap(double latitude, double longitude, int zoom) {
|
private final StorageService storageService;
|
||||||
val client = RestClient.create("https://maps.googleapis.com/maps/api/staticmap");
|
|
||||||
return client.get()
|
public MapServiceImpl(List<MapProvider> mapProviders, StorageService storageService) {
|
||||||
.uri(uriBuilder -> uriBuilder
|
this.storageService = storageService;
|
||||||
.queryParam("center", latitude + "," + longitude)
|
mapProviders.forEach(
|
||||||
.queryParam("zoom", "" + zoom)
|
provider -> providers.put(provider.getClass().getAnnotation(Component.class).value().toUpperCase(), provider));
|
||||||
.queryParam("size", "540x540")
|
}
|
||||||
.queryParam("map_id", "2f371c2346218fe8")
|
|
||||||
.queryParam("key", "AIzaSyARgP_FKFXsrcgVd_HVWoIfH5N8-a88wlQ")
|
public List<String> getMapProviders() {
|
||||||
.build())
|
return List.copyOf(providers.keySet());
|
||||||
.retrieve()
|
}
|
||||||
.body(byte[].class);
|
|
||||||
|
@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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
package com.rdkr.tide_display.backend.images.services.mapProvider;
|
||||||
|
|
||||||
|
public interface MapProvider {
|
||||||
|
|
||||||
|
byte[] getStaticMap(double latitude, double longitude, int zoom);
|
||||||
|
|
||||||
|
}
|
3
backend/src/main/resources/application-dev.yaml
Normal file
3
backend/src/main/resources/application-dev.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.rdkr: trace
|
Reference in New Issue
Block a user