Commit df9bd5b0 authored by Matija Obreza's avatar Matija Obreza

Merge branch '326-tile-server-pretty-dots' into 'master'

Resolve "Tile server: Pretty dots"

Closes #326

See merge request genesys-pgr/genesys-server!221
parents 76f89223 9a96d656
...@@ -26,7 +26,7 @@ import org.genesys2.server.service.impl.FilterHandler.AppliedFilters; ...@@ -26,7 +26,7 @@ import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
public interface MappingService { public interface MappingService {
/** The default tile color. */ /** The default tile color. */
public Color DEFAULT_TILE_COLOR = Color.decode("#88ba41"); public Color DEFAULT_TILE_COLOR = Color.decode("#578218");
/** /**
* Clear the tiles cache. * Clear the tiles cache.
......
...@@ -28,25 +28,29 @@ import java.util.stream.Collectors; ...@@ -28,25 +28,29 @@ import java.util.stream.Collectors;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.sql.DataSource; import javax.sql.DataSource;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
import org.genesys.blocks.model.filters.BasicModelFilter;
import org.genesys2.server.exception.MaxPageLimitException; import org.genesys2.server.exception.MaxPageLimitException;
import org.genesys2.server.model.elastic.AccessionDetails; import org.genesys2.server.model.elastic.AccessionDetails;
import org.genesys2.server.model.genesys.*; import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.Method;
import org.genesys2.server.model.genesys.QAccession;
import org.genesys2.server.model.genesys.QAccessionGeo;
import org.genesys2.server.model.impl.Country; import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.Crop; import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.FaoInstitute; import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.impl.GeoRegion; import org.genesys2.server.model.impl.GeoRegion;
import org.genesys2.server.persistence.AccessionRepository; import org.genesys2.server.persistence.AccessionRepository;
import org.genesys2.server.persistence.MethodRepository; import org.genesys2.server.persistence.MethodRepository;
import org.genesys2.server.service.*; import org.genesys2.server.service.CropService;
import org.genesys2.server.service.ElasticsearchService;
import org.genesys2.server.service.ElasticsearchService.Term; import org.genesys2.server.service.ElasticsearchService.Term;
import org.genesys2.server.service.ElasticsearchService.TermResult; import org.genesys2.server.service.ElasticsearchService.TermResult;
import org.genesys2.server.service.FilterConstants;
import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.GeoRegionService;
import org.genesys2.server.service.GeoService;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.TaxonomyService;
import org.genesys2.server.service.filter.AccessionFilter; import org.genesys2.server.service.filter.AccessionFilter;
import org.genesys2.server.service.filter.AppliedFiltersConverter; import org.genesys2.server.service.filter.AppliedFiltersConverter;
import org.genesys2.server.service.impl.DirectMysqlQuery.MethodResolver; import org.genesys2.server.service.impl.DirectMysqlQuery.MethodResolver;
...@@ -68,6 +72,11 @@ import org.springframework.jdbc.core.RowCallbackHandler; ...@@ -68,6 +72,11 @@ import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class GenesysFilterServiceImpl implements GenesysFilterService { public class GenesysFilterServiceImpl implements GenesysFilterService {
...@@ -444,6 +453,7 @@ public class GenesysFilterServiceImpl implements GenesysFilterService { ...@@ -444,6 +453,7 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
query.select(accessionGeo.longitude, accessionGeo.latitude).distinct().from(accession); query.select(accessionGeo.longitude, accessionGeo.latitude).distinct().from(accession);
BooleanBuilder filt = new BooleanBuilder(); BooleanBuilder filt = new BooleanBuilder();
filt.and(accession.tileIndex.isNotNull());
if (filter != null) { if (filter != null) {
filt.and(filter.buildQuery()); filt.and(filter.buildQuery());
} }
...@@ -467,9 +477,9 @@ public class GenesysFilterServiceImpl implements GenesysFilterService { ...@@ -467,9 +477,9 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
} }
query.where(filt); query.where(filt);
QueryResults<Tuple> results = query.fetchResults(); List<Tuple> results = query.fetch();
return results.getResults().stream() return results.stream()
.map(item -> new Double[]{ .map(item -> new Double[]{
item.get(accessionGeo.longitude), item.get(accessionGeo.longitude),
item.get(accessionGeo.latitude), item.get(accessionGeo.latitude),
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.genesys2.server.service.impl; package org.genesys2.server.service.impl;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
...@@ -23,11 +24,12 @@ import java.io.OutputStream; ...@@ -23,11 +24,12 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.genesys.blocks.model.filters.BasicModelFilter;
import org.genesys2.server.service.GenesysFilterService; import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.MappingService; import org.genesys2.server.service.MappingService;
import org.genesys2.server.service.filter.AccessionFilter; import org.genesys2.server.service.filter.AccessionFilter;
...@@ -35,6 +37,7 @@ import org.genesys2.server.service.impl.FilterHandler.AppliedFilters; ...@@ -35,6 +37,7 @@ import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
import org.genesys2.util.CoordUtil; import org.genesys2.util.CoordUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
...@@ -47,7 +50,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; ...@@ -47,7 +50,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@Service @Service
public class MappingServiceImpl implements MappingService { public class MappingServiceImpl implements MappingService, InitializingBean {
// A 3x3 kernel that blurs an image // A 3x3 kernel that blurs an image
// Kernel kernel = new Kernel(3, 3, new float[] { 0.05f, 0.1f, 0.05f, 0.05f, // Kernel kernel = new Kernel(3, 3, new float[] { 0.05f, 0.1f, 0.05f, 0.05f,
...@@ -76,40 +79,42 @@ public class MappingServiceImpl implements MappingService { ...@@ -76,40 +79,42 @@ public class MappingServiceImpl implements MappingService {
private final ObjectMapper mapper = new ObjectMapper(); private final ObjectMapper mapper = new ObjectMapper();
private List<BufferedImage> zoomTemplates = new ArrayList<>();
@Override @Override
public void filteredKml(AppliedFilters filters, OutputStream outputStream) throws IOException { public void filteredKml(AppliedFilters filters, OutputStream outputStream) throws IOException {
filterService.transformFiltersIfNeed(filters); filterService.transformFiltersIfNeed(filters);
LOG.debug(filters.toString()); LOG.debug(filters.toString());
OutputStreamWriter writer = new OutputStreamWriter(outputStream); OutputStreamWriter writer = new OutputStreamWriter(outputStream);
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
writer.write("<kml xmlns=\"http://www.opengis.net/kml/2.2\">"); writer.write("<kml xmlns=\"http://www.opengis.net/kml/2.2\">");
writer.write("<Document>"); writer.write("<Document>");
final OutputStreamWriter finalWriter = writer; final OutputStreamWriter finalWriter = writer;
filterService.listGeo(filters, null, new RowCallbackHandler() { filterService.listGeo(filters, null, new RowCallbackHandler() {
@Override @Override
public void processRow(final ResultSet rs) throws SQLException { public void processRow(final ResultSet rs) throws SQLException {
try { try {
final StringBuilder stringBuilder = new StringBuilder(); final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<Placemark>"); stringBuilder.append("<Placemark>");
stringBuilder.append("<name>").append(rs.getString("acceNumb")).append("</name>"); stringBuilder.append("<name>").append(rs.getString("acceNumb")).append("</name>");
stringBuilder.append("<description></description>"); stringBuilder.append("<description></description>");
stringBuilder.append("<Point><coordinates>"); stringBuilder.append("<Point><coordinates>");
stringBuilder.append(rs.getDouble("longitude")).append(",").append(rs.getDouble("latitude")); stringBuilder.append(rs.getDouble("longitude")).append(",").append(rs.getDouble("latitude"));
stringBuilder.append("</coordinates></Point>"); stringBuilder.append("</coordinates></Point>");
stringBuilder.append("</Placemark>"); stringBuilder.append("</Placemark>");
finalWriter.write(stringBuilder.toString()); finalWriter.write(stringBuilder.toString());
finalWriter.flush(); finalWriter.flush();
} catch (final SQLException e) { } catch (final SQLException e) {
LOG.warn(e.getMessage()); LOG.warn(e.getMessage());
throw e; throw e;
} catch (final IOException e) { } catch (final IOException e) {
LOG.warn(e.getMessage()); LOG.warn(e.getMessage());
} }
} }
}); });
finalWriter.write("</Document></kml>"); finalWriter.write("</Document></kml>");
finalWriter.flush(); finalWriter.flush();
} }
@Override @Override
...@@ -165,34 +170,86 @@ public class MappingServiceImpl implements MappingService { ...@@ -165,34 +170,86 @@ public class MappingServiceImpl implements MappingService {
@Cacheable(value = "tileserver", key = "'tile-' + #zoom + '-' + #xtile + '-' + #ytile + '-' + #filters") @Cacheable(value = "tileserver", key = "'tile-' + #zoom + '-' + #xtile + '-' + #ytile + '-' + #filters")
public byte[] getTile(AccessionFilter filters, int zoom, int xtile, int ytile) { public byte[] getTile(AccessionFilter filters, int zoom, int xtile, int ytile) {
final BufferedImage bufferedImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB); final BufferedImage bufferedImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
final int[] pixelSizes = new int[] { 0, 0, 0, 1, 1, 2, 2, 2, 3, 2, 2, 3 }; BufferedImage accessionDot = zoomTemplates.get(zoomTemplates.size() > zoom ? zoom : zoomTemplates.size() - 1);
final int pixelSize = pixelSizes[zoom >= pixelSizes.length ? pixelSizes.length - 1 : zoom];
final int colorWithoutAlpha = MappingService.DEFAULT_TILE_COLOR.getRGB();
List<Double[]> geoTiles = filterService.listGeoTile(filters, null, zoom, xtile, ytile); List<Double[]> geoTiles = filterService.listGeoTile(filters, null, zoom, xtile, ytile);
geoTiles.forEach(item -> { AtomicInteger paints = new AtomicInteger(0);
final int longitude = CoordUtil.lonToImg3(zoom, xtile, item[0]); geoTiles.stream().map(item -> new TilePos(zoom, xtile, ytile, item)).distinct().forEach(item -> {
final int latitude = CoordUtil.latToImg3(zoom, ytile, item[1]); paints.incrementAndGet();
for (int i = -pixelSize / 2; i <= pixelSize / 2; i++) { // calculates the coordinate where the image is painted
for (int j = -pixelSize / 2; j <= pixelSize / 2; j++) { int topLeftX = item.longitude - accessionDot.getWidth() / 2;
if (longitude + i >= 0 && latitude + j >= 0 && longitude + i < 256 && latitude + j < 256) { int topLeftY = item.latitude - accessionDot.getHeight() / 2;
bufferedImage.setRGB(longitude + i, latitude + j, colorWithoutAlpha);
} // paints the image watermark
} g2d.drawImage(accessionDot, topLeftX, topLeftY, null);
}
}); });
try { LOG.trace("zoom={} dot={}x{} paints={} of={}", zoom, accessionDot.getWidth(), accessionDot.getHeight(), paints.intValue(), geoTiles.size());
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", baos); try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", baos);
return baos.toByteArray(); return baos.toByteArray();
} catch (final IOException e) { } catch (final IOException e) {
LOG.warn(e.getMessage(), e); LOG.warn(e.getMessage(), e);
throw new RuntimeException("Couldn't render image", e); throw new RuntimeException("Couldn't render image", e);
} finally {
g2d.dispose();
}
}
@Override
public void afterPropertiesSet() throws Exception {
for (int i = 0; i < 20; i++) {
String accessionDotSource = "/tileserver/accessionDot" + i + ".png";
try {
BufferedImage accessionAtZoom = ImageIO.read(this.getClass().getResourceAsStream(accessionDotSource));
zoomTemplates.add(accessionAtZoom);
} catch (Throwable e) {
LOG.warn("Could not read accession time template {}: {}", accessionDotSource, e.getMessage());
}
}
}
/**
* Allows us to generate distinct dot locations
*/
private static class TilePos {
private int longitude;
private int latitude;
public TilePos(int zoom, int xtile, int ytile, Double[] item) {
this.longitude = CoordUtil.lonToImg3(zoom, xtile, item[0]);
this.latitude = CoordUtil.latToImg3(zoom, ytile, item[1]);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + latitude;
result = prime * result + longitude;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TilePos other = (TilePos) obj;
if (latitude != other.latitude)
return false;
if (longitude != other.longitude)
return false;
return true;
} }
} }
} }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment