/******************************************************************************* * Copyright 2013-2016 alladin-IT GmbH * * 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 at.alladin.rmbt.mapServer; import java.io.ByteArrayOutputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Arrays; import java.util.List; import javax.imageio.ImageIO; import org.restlet.data.Form; import at.alladin.rmbt.mapServer.MapServerOptions.MapOption; import at.alladin.rmbt.mapServer.MapServerOptions.SQLFilter; import at.alladin.rmbt.mapServer.parameters.HeatmapTileParameters; import at.alladin.rmbt.mapServer.parameters.TileParameters; public class HeatmapTiles extends TileRestlet<HeatmapTileParameters> { private final static int[] ZOOM_TO_PART_FACTOR = new int[] { // factor | zoomlevel 0, // 0 0, // 1 0, // 2 0, // 3 0, // 4 0, // 5 0, // 6 1, // 7 1, // 8 2, // 9 2, // 10 3, // 11 3, // 12 4, // 13 4, // 14 5, // 15 5, // 16 6, // 17 6, // 18 7, // 19 7, // 20 }; private final static double ALPHA_TOP = 0.5; private final static int ALPHA_MAX = 1; private final static boolean DEBUG_LINES = false; private final static int HORIZON_OFFSET = 1; private final static int HORIZON = HORIZON_OFFSET * 2 + 2; private final static int HORIZON_SIZE = HORIZON * HORIZON; private final static double[][] FACTORS = new double[8][]; // lookup table // for speedup static { for (int f = 0; f < 8; f++) { final int partSize = 1 << f; FACTORS[f] = new double[HORIZON_SIZE * partSize * partSize]; for (int i = 0; i < FACTORS[f].length; i += HORIZON_SIZE) { final double qPi = Math.PI / 4; final double x = qPi * (i / HORIZON_SIZE % partSize) / partSize; final double y = qPi * (i / HORIZON_SIZE / partSize) / partSize; // double sum = 0; for (int j = 0; j < HORIZON; j++) for (int k = 0; k < HORIZON; k++) { final double value = Math.pow(Math.cos(x + (1 - j) * qPi), 2.0) * Math.pow(Math.cos(y + (1 - k) * qPi), 2.0) / 4; FACTORS[f][i + j + k * HORIZON] = value; // sum += value; } } } } @SuppressWarnings("unchecked") private final ThreadLocal<int[]>[] pixelBuffers = new ThreadLocal[TILE_SIZES.length]; public HeatmapTiles() { for (int i = 0; i < TILE_SIZES.length; i++) { final int tileSize = TILE_SIZES[i]; pixelBuffers[i] = new ThreadLocal<int[]>() { @Override protected int[] initialValue() { return new int[tileSize * tileSize]; }; }; } } @Override protected HeatmapTileParameters getTileParameters(TileParameters.Path path, Form params) { return new HeatmapTileParameters(path, params); } @Override protected byte[] generateTile(final HeatmapTileParameters params, final int tileSizeIdx, final int zoom, final DBox box, final MapOption mo, final List<SQLFilter> filters, final float quantile) { filters.add(MapServerOptions.getAccuracyMapFilter()); final int tileSize = TILE_SIZES[tileSizeIdx]; final double transparency = params.getTransparency(); final StringBuilder whereSQL = new StringBuilder(mo.sqlFilter); for (final SQLFilter sf : filters) whereSQL.append(" AND ").append(sf.where); final String sql = String.format("SELECT count(\"%1$s\") count," + " quantile(\"%1$s\",?) val," + " ST_X(ST_SnapToGrid(location, ?,?,?,?)) gx," + " ST_Y(ST_SnapToGrid(location, ?,?,?,?)) gy" + " FROM v_test2 t" + " WHERE " + " %2$s" + " AND location && ST_SetSRID(ST_MakeBox2D(ST_Point(?,?), ST_Point(?,?)), 900913)" + " GROUP BY gx,gy", mo.valueColumnLog, whereSQL); final int partSizeFactor; if (zoom >= ZOOM_TO_PART_FACTOR.length) partSizeFactor = ZOOM_TO_PART_FACTOR[ZOOM_TO_PART_FACTOR.length - 1]; else partSizeFactor = ZOOM_TO_PART_FACTOR[zoom]; final int partSizePixels = 1 << partSizeFactor; final int fetchPartsX = tileSize / partSizePixels + (HORIZON_OFFSET + 2) * 2; final int fetchPartsY = tileSize / partSizePixels + (HORIZON_OFFSET + 2) * 2; final double[] values = new double[fetchPartsX * fetchPartsY]; // final int[] countsReal = new int[fetchPartsX * fetchPartsY]; final int[] countsRel = new int[fetchPartsX * fetchPartsY]; Arrays.fill(values, Double.NaN); boolean _emptyTile = true; try (Connection con = DbConnection.getConnection()) { try (PreparedStatement ps = con.prepareStatement(sql)) { int p = 1; ps.setFloat(p++, quantile); // int _partSizeFactor = (int)Math.round((8d/11d) * zoom - // (48d/11d)); // if (_partSizeFactor < 0) // _partSizeFactor = 0; // if (_partSizeFactor > 7) // _partSizeFactor = 7; // final int partSizeFactor = _partSizeFactor; // System.out.println(partSizePixels); final double partSize = box.res * partSizePixels; final double origX = box.x1 - box.res * (partSizePixels / 2) - partSize * (HORIZON_OFFSET + 1); final double origY = box.y1 - box.res * (partSizePixels / 2) - partSize * (HORIZON_OFFSET + 1); for (int j = 0; j < 2; j++) { ps.setDouble(p++, origX); ps.setDouble(p++, origY); ps.setDouble(p++, partSize); ps.setDouble(p++, partSize); } for (final SQLFilter sf : filters) p = sf.fillParams(p, ps); final double margin = partSize * (HORIZON_OFFSET + 1); ps.setDouble(p++, box.x1 - margin); ps.setDouble(p++, box.y1 - margin); ps.setDouble(p++, box.x2 + margin); ps.setDouble(p++, box.y2 + margin); // System.out.println(ps); if (!ps.execute()) throw new IllegalArgumentException(ps.getWarnings()); try (ResultSet rs = ps.getResultSet()) { while (rs.next()) { _emptyTile = false; int count = rs.getInt(1); final double val = rs.getDouble(2); final double gx = rs.getDouble(3); final double gy = rs.getDouble(4); final int mx = (int) Math.round((gx - origX) / partSize); final int my = (int) Math.round((gy - origY) / partSize); // System.out.println(String.format("%f|%f %d|%d %d %f",gx, gy, // mx, my, count, val)); if (mx >= 0 && mx < fetchPartsX && my >= 0 && my < fetchPartsY) { final int idx = mx + fetchPartsX * (fetchPartsY - 1 - my); values[idx] = val; // countsReal[idx] = count; if (count > ALPHA_MAX) count = ALPHA_MAX; countsRel[idx] = count; } } } } if (_emptyTile) return null; final Image img = images[tileSizeIdx].get(); final int[] pixels = pixelBuffers[tileSizeIdx].get(); for (int y = 0; y < tileSize; y++) for (int x = 0; x < tileSize; x++) { final int mx = HORIZON_OFFSET + 1 + (x + partSizePixels / 2) / partSizePixels; final int my = HORIZON_OFFSET + 1 + (y + partSizePixels / 2) / partSizePixels; final int relX = (x + partSizePixels / 2) % partSizePixels; final int relY = (y + partSizePixels / 2) % partSizePixels; final int relOffset = (relY * partSizePixels + relX) * HORIZON_SIZE; double alphaWeigth = 0; double valueWeight = 0; double valueMissing = 0; final int startIdx = mx - HORIZON_OFFSET + fetchPartsX * (my - HORIZON_OFFSET); for (int i = 0; i < HORIZON_SIZE; i++) { final int idx = startIdx + i % HORIZON + fetchPartsX * (i / HORIZON); if (Double.isNaN(values[idx])) valueMissing += FACTORS[partSizeFactor][i + relOffset]; else valueWeight += FACTORS[partSizeFactor][i + relOffset] * values[idx]; alphaWeigth += FACTORS[partSizeFactor][i + relOffset] * countsRel[idx]; } if (valueMissing > 0) valueWeight += valueWeight / (1 - valueMissing) * valueMissing; alphaWeigth /= ALPHA_TOP; if (alphaWeigth < 0) alphaWeigth = 0; if (alphaWeigth > 1) alphaWeigth = 1; alphaWeigth *= transparency; final int alpha = (int) (alphaWeigth * 255) << 24; assert alpha >= 0 || alpha <= 255 : alpha; if (alpha == 0) pixels[x + y * tileSize] = 0; else pixels[x + y * tileSize] = valueToColor(mo.colorsSorted, mo.intervalsSorted, valueWeight) | alpha; // pixels[x + y * WIDTH] = 255 << 24 | alpha >>> 8 | // alpha >>> 16 | alpha >>> 24; if (DEBUG_LINES) if (relX == partSizePixels / 2 || relY == partSizePixels / 2) pixels[x + y * tileSize] = 0xff000000; } img.bi.setRGB(0, 0, tileSize, tileSize, pixels, 0, tileSize); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(img.bi, "png", baos); return baos.toByteArray(); } catch (final Exception e) { e.printStackTrace(); throw new IllegalStateException(e); } } }