Commit c16f39a8 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '303-tilecontroller' into 'master'

Resolve "TileController"

Closes #303

See merge request genesys-pgr/genesys-server!198
parents e9f1b92d d24c9752
......@@ -151,7 +151,7 @@ public class ShortFilterServiceImpl implements ShortFilterService {
@Override
public <T> T filterByCode(String code, Class<T> clazz) throws IOException {
ShortFilter shortFilter = shortFilterRepository.findByCode(code);
ShortFilter shortFilter = shortFilterRepository.findByCode(code == null ? "" : code);
return mapper.readValue(shortFilter.getJson(), clazz);
}
......
/*
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys2.server.api.v1;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.genesys.catalog.service.ShortFilterService;
import org.genesys2.server.service.MappingService;
import org.genesys2.server.service.filter.AccessionFilter;
import org.genesys2.util.ColorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* This is not an API controller, it's accessible to anonymous users.
*/
@Controller
public class TileController {
@Autowired
private MappingService mappingService;
@Autowired
private ShortFilterService shortFilterService;
@RequestMapping(value = "/acn/tile/{zoom:\\d+}/{x:\\d+}/{y:\\d+}", produces = MediaType.IMAGE_PNG_VALUE)
public void tile(@RequestParam(value = "f", required = false, defaultValue="") String filterCode,
@RequestParam(value = "color", required = false) String color,
@PathVariable("x") int x, @PathVariable("y") int y, @PathVariable("zoom") int zoom,
HttpServletResponse response) throws IOException {
AccessionFilter appliedFilters = shortFilterService.filterByCode(filterCode, AccessionFilter.class);
byte[] image = mappingService.getTile(appliedFilters, zoom, x, y);
image = ColorUtil.changeColor(color, image);
response.setContentType(MediaType.IMAGE_PNG_VALUE);
response.setContentLength(image.length);
response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, s-maxage=3600, public, no-transform");
response.getOutputStream().write(image, 0, image.length);
}
}
......@@ -16,10 +16,6 @@
package org.genesys2.server.mvc;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
......@@ -34,7 +30,6 @@ import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
......@@ -74,6 +69,7 @@ import org.genesys2.server.service.impl.GenesysFilterServiceImpl.LabelValue;
import org.genesys2.server.service.impl.SearchException;
import org.genesys2.server.servlet.model.ErrorResponse;
import org.genesys2.spring.ResourceNotFoundException;
import org.genesys2.util.ColorUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
......@@ -97,7 +93,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.support.RequestContextUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jhlabs.image.MapColorsFilter;
@Controller
public class ExplorerController extends BaseController implements InitializingBean {
......@@ -794,9 +789,11 @@ public class ExplorerController extends BaseController implements InitializingBe
try {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
byte[] image = mappingService.getTile(appliedFilters, zoom, x, y);
image = changeColor(color, image);
byte[] image = mappingService.getTile(AccessionFilter.convert(appliedFilters), zoom, x, y);
image = ColorUtil.changeColor(color, image);
response.setContentType(MediaType.IMAGE_PNG_VALUE);
response.setContentLength(image.length);
response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, s-maxage=3600, public, no-transform");
response.getOutputStream().write(image, 0, image.length);
......@@ -816,44 +813,6 @@ public class ExplorerController extends BaseController implements InitializingBe
return geoService.getBoundingBox(appliedFilters);
}
private byte[] changeColor(String color, byte[] imageBytes) {
if (StringUtils.isBlank(color)) {
return imageBytes;
}
if (!color.startsWith("#"))
color = "#" + color;
if (LOG.isDebugEnabled())
LOG.debug("Changing color to {}", color);
try {
final Color newColor = Color.decode(color);
if (newColor.equals(MappingService.DEFAULT_TILE_COLOR)) {
return imageBytes;
}
final int originalColor = MappingService.DEFAULT_TILE_COLOR.getRGB();
final int updatedColor = newColor.getRGB();
final MapColorsFilter mcf = new MapColorsFilter(originalColor, updatedColor);
final ByteArrayInputStream bios = new ByteArrayInputStream(imageBytes);
final BufferedImage image = mcf.filter(ImageIO.read(bios), null);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", baos);
return baos.toByteArray();
} catch (final NumberFormatException e) {
LOG.warn("Cannot get color for {}", color);
return imageBytes;
} catch (final IOException e) {
LOG.warn(e.getMessage());
return imageBytes;
}
}
private void overviewInstitutes(ModelMap model, AppliedFilters appliedFilters) throws SearchException {
model.addAttribute("statsInstCode", elasticService.termStatisticsAuto(Accession.class, AccessionFilter.convert(appliedFilters), 20, AppliedFiltersConverter.convertTerm(FilterConstants.INSTCODE)));
model.addAttribute("statsInstCountry", elasticService.termStatisticsAuto(Accession.class, AccessionFilter.convert(appliedFilters), 20, AppliedFiltersConverter.convertTerm(FilterConstants.INSTITUTE_COUNTRY_ISO3)));
......
......@@ -20,12 +20,14 @@ import java.util.List;
import org.genesys2.server.model.elastic.AccessionDetails;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.service.filter.AccessionFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
import org.genesys2.server.service.impl.GenesysFilterServiceImpl.LabelValue;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.RowCallbackHandler;
// TODO Deprecate
public interface GenesysFilterService {
Page<Accession> listAccessions(AppliedFilters filters, Pageable pageable);
......@@ -41,5 +43,7 @@ public interface GenesysFilterService {
void listGeoTile(boolean distinct, AppliedFilters filters, Integer limit, int zoom, int xtile, int ytile, RowCallbackHandler rowHandler);
List<Double[]> listGeoTile(AccessionFilter filter, Integer limit, int zoom, int xtile, int ytile);
AppliedFilters transformFiltersIfNeed(AppliedFilters appliedFilters);
}
......@@ -20,6 +20,7 @@ import java.awt.Color;
import java.io.IOException;
import java.io.OutputStream;
import org.genesys2.server.service.filter.AccessionFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
public interface MappingService {
......@@ -66,154 +67,5 @@ public interface MappingService {
* the ytile
* @return the tile
*/
byte[] getTile(AppliedFilters filters, int zoom, int xtile, int ytile);
/**
* Coordinate utilities
*/
public static class CoordUtil {
/**
* Convert tile index at zoom level to longitude.
*
* @param zoom
* zoom level
* @param xtile
* tile index
* @return longitude of left side of tile
*/
public static double tileToLon(final int zoom, final int xtile) {
return 360.0 * xtile / (1 << zoom) - 180.0;
}
/**
* Convert tile index at zoom level to latitude
*
* @param zoom
* zoom level
* @param ytile
* tile index
* @return latitude of the top side of the tile
*/
public static double tileToLat(final int zoom, final int ytile) {
final double n = Math.PI - 2.0 * Math.PI * ytile / (1 << zoom);
return Math.toDegrees(Math.atan(Math.sinh(n)));
}
/**
* Lon to tile.
*
* @param zoom
* the zoom
* @param lon
* the lon
* @return the int
*/
public static int lonToTile(final int zoom, final double lon) {
return (int) Math.floor((lon + 180.0) / 360.0 * (1 << zoom));
}
/**
* Lat to tile.
*
* @param zoom
* the zoom
* @param lat
* the lat
* @return the int
*/
public static int latToTile(final int zoom, final double lat) {
final double latr = Math.toRadians(lat);
return (int) Math.floor((1 - Math.log(Math.tan(latr) + 1 / Math.cos(latr)) / Math.PI) / 2 * (1 << zoom));
}
/**
* Lon to img.
*
* @param zoom
* the zoom
* @param lon
* the lon
* @return the int
*/
public static int lonToImg(final int zoom, double lon) {
// n = 2 ^ zoom
final int xtile = lonToTile(zoom, lon);
final double a = tileToLon(zoom, xtile);
final double b = tileToLon(zoom, xtile + 1);
// System.err.println("a=" + a + " b=" + b);
// Translate to start of tile
lon -= a;
// Scale by (ba)*256;
// System.err.println("b-a="+(b-a));
lon = 256.0 * lon / (b - a);
return (int) Math.floor(lon);
}
/**
* Lat to img.
*
* @param zoom
* the zoom
* @param lat
* the lat
* @return the int
*/
public static int latToImg(final int zoom, double lat) {
// n = 2 ^ zoom
final int ytile = latToTile(zoom, lat);
// System.err.println("ytile=" + ytile);
final double a = tileToLat(zoom, ytile);
final double b = tileToLat(zoom, ytile + 1);
// System.err.println("a=" + a + " b=" + b);
// Translate to start of tile
lat -= a;
// Scale by (ba)*256;
// System.err.println("b-a=" + (b - a));
lat = 256.0 * lat / (b - a);
return (int) Math.floor(lat);
}
/**
* Mercator projection of decimal latitude to a y-pixel on tile at
* specific zoom level.
*
* @param zoom
* Zoom level
* @param tile
* Tile index
* @param lat
* Latitude
* @return the int
*/
public static int latToImg3(final int zoom, final int tile, double lat) {
lat = Math.min(85.0511287, Math.max(lat, -85.0511287));
final double pixAtZoom = (1 << zoom) * 256;
final double latr = Math.toRadians(lat);
final double y1 = Math.floor((1 - Math.log(Math.tan(latr) + 1 / Math.cos(latr)) / Math.PI) / 2 * pixAtZoom);
return (int) y1 - tile * 256;
}
/**
* Convert decimal longitude to an x-pixel on tile at specific zoom
* level.
*
* @param zoom
* Zoom level
* @param tile
* Tile index
* @param lng
* Longitude
* @return the int
*/
public static int lonToImg3(final int zoom, final int tile, final double lng) {
final double totalPixelsAtZoom = (1 << zoom) * 256;
final double longitudePixelAtZoom = Math.floor((180.0 + lng) / 360.0 * totalPixelsAtZoom);
return (int) (longitudePixelAtZoom - tile * 256);
}
}
byte[] getTile(AccessionFilter filter, int zoom, int xtile, int ytile);
}
......@@ -28,7 +28,6 @@ import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.genesys2.server.model.genesys.Method;
import org.genesys2.server.service.FilterConstants;
import org.genesys2.server.service.MappingService.CoordUtil;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
import org.genesys2.server.service.impl.FilterHandler.FilterValue;
......@@ -37,6 +36,7 @@ import org.genesys2.server.service.impl.FilterHandler.MaxValueFilter;
import org.genesys2.server.service.impl.FilterHandler.MinValueFilter;
import org.genesys2.server.service.impl.FilterHandler.StartsWithFilter;
import org.genesys2.server.service.impl.FilterHandler.ValueRangeFilter;
import org.genesys2.util.CoordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
......
......@@ -23,30 +23,30 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
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.model.elastic.AccessionDetails;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.Method;
import org.genesys2.server.model.genesys.*;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.impl.GeoRegion;
import org.genesys2.server.persistence.AccessionRepository;
import org.genesys2.server.persistence.MethodRepository;
import org.genesys2.server.service.CropService;
import org.genesys2.server.service.ElasticsearchService;
import org.genesys2.server.service.*;
import org.genesys2.server.service.ElasticsearchService.Term;
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.AppliedFiltersConverter;
import org.genesys2.server.service.impl.DirectMysqlQuery.MethodResolver;
......@@ -54,6 +54,7 @@ import org.genesys2.server.service.impl.FilterHandler.AppliedFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
import org.genesys2.server.service.impl.FilterHandler.FilterValue;
import org.genesys2.server.service.impl.FilterHandler.LiteralValueFilter;
import org.genesys2.util.CoordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -88,8 +89,8 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
@Autowired
private GeoService geoService;
// @PersistenceContext
// private EntityManager entityManager;
@Autowired
private EntityManager entityManager;
private JdbcTemplate jdbcTemplate;
......@@ -389,6 +390,9 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
listGeoTile(false, filters, limit, -1, 0, 0, rowHandler);
}
/**
* @deprecated Use {@link #listGeoTile(AccessionFilter, Integer, int, int, int)}
*/
@Override
public void listGeoTile(final boolean distinct, AppliedFilters filters, Integer limit, int zoom, int xtile, int ytile, RowCallbackHandler rowHandler) {
if (LOG.isDebugEnabled()) {
......@@ -431,6 +435,47 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
}, rowHandler);
}
@Override
public List<Double[]> listGeoTile(AccessionFilter filter, Integer limit, int zoom, int xtile, int ytile) {
QAccession accession = QAccession.accession;
QAccessionGeo accessionGeo = accession.accessionId.geo;
JPQLQuery<Tuple> query = new JPAQuery<>(entityManager);
query.select(accessionGeo.longitude, accessionGeo.latitude).distinct().from(accession);
BooleanBuilder filt = new BooleanBuilder();
if (filter != null) {
filt.and(filter.buildQuery());
}
// The tile query
{
final double lonW = CoordUtil.tileToLon(zoom, xtile);
final double lonE = CoordUtil.tileToLon(zoom, xtile + 1);
final double diffLon = lonE - lonW;
final double latN = CoordUtil.tileToLat(zoom, ytile);
final double latS = CoordUtil.tileToLat(zoom, ytile + 1);
final double diffLat = latN - latS;
if (LOG.isDebugEnabled()) {
LOG.debug("{} <= lon <= {} corr={}", lonW, lonE, diffLon * .2);
LOG.debug("{} <= lat <= {} corr={}", latS, latN, diffLat * .2);
}
filt.and(accessionGeo.longitude.between(lonW - zoom * diffLon * .2, lonE + zoom * diffLon * .2));
filt.and(accessionGeo.latitude.between(latS - zoom * diffLat * .2, latN + zoom * diffLat * .2));
}
query.where(filt);
QueryResults<Tuple> results = query.fetchResults();
return results.getResults().stream()
.map(item -> new Double[]{
item.get(accessionGeo.longitude),
item.get(accessionGeo.latitude),
}).collect(Collectors.toList());
}
@Override
public AppliedFilters transformFiltersIfNeed(final AppliedFilters appliedFilters) {
AppliedFilter regionOriginFilter = appliedFilters.stream().filter(filter -> filter.getFilterName().equals("regionOrigin")).findFirst().orElse(null);
......
......@@ -23,12 +23,16 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.imageio.ImageIO;
import org.genesys.blocks.model.filters.BasicModelFilter;
import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.MappingService;
import org.genesys2.server.service.filter.AccessionFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
import org.genesys2.util.CoordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -159,74 +163,36 @@ public class MappingServiceImpl implements MappingService {
@Override
@Cacheable(value = "tileserver", key = "'tile-' + #zoom + '-' + #xtile + '-' + #ytile + '-' + #filters")
public byte[] getTile(AppliedFilters filters, final int zoom, final int xtile, final int ytile) {
filterService.transformFiltersIfNeed(filters);
final BufferedImage bi = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
public byte[] getTile(AccessionFilter filters, int zoom, int xtile, int ytile) {
final BufferedImage bufferedImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
// final int pixelSize = (int) Math.round(1.0 + (1 << (zoom / 2))) / 2;
final int[] pixelSizes = new int[] { 0, 0, 0, 1, 1, 2, 2, 2, 3, 2, 2, 3 };
final int pixelSize = pixelSizes[zoom >= pixelSizes.length ? pixelSizes.length - 1 : zoom];
if (LOG.isDebugEnabled()) {
LOG.debug(filters.toString());
LOG.debug("PIXELSIZE={} zoom={}", pixelSize, zoom);
}
// // Border
// for (int i = 0; i < 256; i++) {
// bi.setRGB(0, i, Color.red.getRGB());
// bi.setRGB(i, 0, Color.red.getRGB());
// bi.setRGB(255, i, Color.red.getRGB());
// bi.setRGB(i, 255, Color.red.getRGB());
// }
final int colorWithoutAlpha = MappingService.DEFAULT_TILE_COLOR.getRGB();
filterService.listGeoTile(true, filters, null, zoom, xtile, ytile, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
try {
final int longitude = CoordUtil.lonToImg3(zoom, xtile, rs.getDouble("longitude"));
final int latitude = CoordUtil.latToImg3(zoom, ytile, rs.getDouble("latitude"));
List<Double[]> geoTiles = filterService.listGeoTile(filters, null, zoom, xtile, ytile);
if (LOG.isTraceEnabled()) {
LOG.trace("Adding geo={}, {} as pixe=[{}, {}]", rs.getDouble("longitude"), rs.getDouble("latitude"), longitude, latitude);
}
geoTiles.forEach(item -> {
final int longitude = CoordUtil.lonToImg3(zoom, xtile, item[0]);
final int latitude = CoordUtil.latToImg3(zoom, ytile, item[1]);
for (int i = -pixelSize / 2; i <= pixelSize / 2; i++) {
for (int j = -pixelSize / 2; j <= pixelSize / 2; j++) {
if (longitude + i >= 0 && latitude + j >= 0 && longitude + i < 256 && latitude + j < 256) {
bi.setRGB(longitude + i, latitude + j, colorWithoutAlpha);
}
}
for (int i = -pixelSize / 2; i <= pixelSize / 2; i++) {
for (int j = -pixelSize / 2; j <= pixelSize / 2; j++) {
if (longitude + i >= 0 && latitude + j >= 0 && longitude + i < 256 && latitude + j < 256) {
bufferedImage.setRGB(longitude + i, latitude + j, colorWithoutAlpha);
}
} catch (final SQLException e) {
LOG.warn(e.getMessage());
throw e;
}
}
});
// for (int j = -80; j < 81; j += 10) {
// int latitude = CoordUtil.latToImg3(zoom, ytile, j);
// if (latitude >= 0 && latitude < 256)
// for (int i = 0; i < 256; i++) {
// bi.setRGB(i, latitude, Color.blue.getRGB());
// }
// }
//
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
// if (zoom < -20) {