TileMaker.java
package org.gringlobal.service.impl;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.time.StopWatch;
import org.gringlobal.util.CoordUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* Handles accession tile generation for maps.
*/
@Component
@Slf4j
public class TileMaker implements InitializingBean {
private List<BufferedImage> zoomTemplates = new ArrayList<>();
@Override
public void afterPropertiesSet() throws Exception {
for (int i = 0; i < 15; i++) {
String accessionDotSource = "/tileserver/accessionDot" + i + ".png";
try (InputStream sourceStream = this.getClass().getResourceAsStream(accessionDotSource)) {
BufferedImage accessionAtZoom = ImageIO.read(sourceStream);
zoomTemplates.add(accessionAtZoom);
} catch (Throwable e) {
log.warn("Could not read accession time template {}: {}", accessionDotSource, e.getMessage());
}
}
}
/**
* Generate a tile from source
* @param zoom
* @param xtile
* @param ytile
* @param coordinateSource Make sure coordinates are ordered by lat,lon
* @return
*/
public byte[] makeTile(int zoom, int xtile, int ytile, Supplier<Stream<Double[]>> coordinateSource) {
StopWatch stopWatch = StopWatch.createStarted();
final BufferedImage bufferedImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
BufferedImage accessionDot = zoomTemplates.get(zoomTemplates.size() > zoom ? zoom : zoomTemplates.size() - 1);
int dotHalfW = accessionDot.getWidth() / 2;
int dotHalfH = accessionDot.getHeight() / 2;
AtomicInteger paints = new AtomicInteger(0);
AtomicInteger outsidesX = new AtomicInteger(0);
AtomicInteger outsidesY = new AtomicInteger(0);
coordinateSource.get().map(item -> new TilePos(zoom, xtile, ytile, item)).forEach(item -> {
// calculates the coordinate where the image is painted
int topLeftX = item.longitude - dotHalfW;
int topLeftY = item.latitude - dotHalfH;
if (log.isDebugEnabled()) {
paints.incrementAndGet();
if (topLeftX < -dotHalfW || topLeftX > 255 + dotHalfW) {
log.trace("Longitude {},{} outside 0 - 255", item.longitude, item.latitude);
outsidesX.incrementAndGet();
}
if (topLeftY < -dotHalfH || topLeftY > 255 + dotHalfH) {
log.trace("Latitude {},{} outside 0 - 255", item.longitude, item.latitude);
outsidesY.incrementAndGet();
}
}
// paints the image watermark
g2d.drawImage(accessionDot, topLeftX, topLeftY, null);
});
stopWatch.split();
log.debug("Painted outX={} outY={} {} in {}", outsidesX.get(), outsidesY.get(), paints.get(), stopWatch.toSplitString());
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", baos);
return baos.toByteArray();
} catch (final IOException e) {
log.warn(e.getMessage(), e);
throw new RuntimeException("Couldn't render image", e);
} finally {
g2d.dispose();
}
}
/**
* Allows us to generate distinct dot locations
*/
@Data
private static class TilePos {
private int longitude;
private int latitude;
public TilePos(int zoom, int xtile, int ytile, Double[] item) {
this.latitude = CoordUtil.latToImg3(zoom, ytile, item[0]);
this.longitude = CoordUtil.lonToImg3(zoom, xtile, item[1]);
}
}
}