package mil.nga.giat.geowave.adapter.vector.render;
import java.awt.Color;
import java.awt.image.IndexColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.remote.SerializableState;
import javax.media.jai.remote.SerializerFactory;
import javax.xml.transform.TransformerException;
import org.geoserver.wms.DefaultWebMapService;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.SLDParser;
import org.geotools.styling.SLDTransformer;
import org.geotools.styling.Style;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import mil.nga.giat.geowave.adapter.vector.plugin.GeoWaveGTDataStore;
import mil.nga.giat.geowave.core.index.Persistable;
import mil.nga.giat.geowave.core.index.StringUtils;
public class DistributedRenderOptions implements
Persistable
{
private static final Logger LOGGER = LoggerFactory.getLogger(DistributedRenderOptions.class);
// it doesn't make sense to grab this from the context of the geoserver
// settings, although it is unclear whether in distributed rendering this
// should be enabled or disabled by default
private final static boolean USE_GLOBAL_RENDER_POOL = true;
private String antialias;
private boolean continuousMapWrapping;
private boolean advancedProjectionHandlingEnabled;
private boolean optimizeLineWidth;
private boolean transparent;
private boolean isMetatile;
private boolean kmlPlacemark;
private boolean renderScaleMethodAccurate;
private int mapWidth;
private int mapHeight;
private int buffer;
private double angle;
private IndexColorModel palette;
private Color bgColor;
private int maxRenderTime;
private int maxErrors;
private int maxFilters;
private ReferencedEnvelope envelope;
private int wmsIterpolationOrdinal;
private List<Integer> interpolationOrdinals;
private Style style;
protected DistributedRenderOptions() {}
public DistributedRenderOptions(
final WMS wms,
final WMSMapContent mapContent,
final Style style ) {
optimizeLineWidth = DefaultWebMapService.isLineWidthOptimizationEnabled();
maxFilters = DefaultWebMapService.getMaxFilterRules();
transparent = mapContent.isTransparent();
buffer = mapContent.getBuffer();
angle = mapContent.getAngle();
mapWidth = mapContent.getMapWidth();
mapHeight = mapContent.getMapHeight();
bgColor = mapContent.getBgColor();
palette = mapContent.getPalette();
renderScaleMethodAccurate = StreamingRenderer.SCALE_ACCURATE.equals(mapContent.getRendererScaleMethod());
wmsIterpolationOrdinal = wms.getInterpolation().ordinal();
maxErrors = wms.getMaxRenderingErrors();
this.style = style;
envelope = mapContent.getRenderingArea();
final GetMapRequest request = mapContent.getRequest();
final Object timeoutOption = request.getFormatOptions().get(
"timeout");
int localMaxRenderTime = 0;
if (timeoutOption != null) {
try {
// local render time is in millis, while WMS max render time is
// in seconds
localMaxRenderTime = Integer.parseInt(timeoutOption.toString()) / 1000;
}
catch (final NumberFormatException e) {
LOGGER.warn(
"Could not parse format_option \"timeout\": " + timeoutOption,
e);
}
}
maxRenderTime = getMaxRenderTime(
localMaxRenderTime,
wms);
isMetatile = request.isTiled() && (request.getTilesOrigin() != null);
final Object antialiasObj = request.getFormatOptions().get(
"antialias");
if (antialiasObj != null) {
antialias = antialiasObj.toString();
}
if (request.getFormatOptions().get(
"kmplacemark") != null) {
kmlPlacemark = ((Boolean) request.getFormatOptions().get(
"kmplacemark")).booleanValue();
}
// turn on advanced projection handling
advancedProjectionHandlingEnabled = wms.isAdvancedProjectionHandlingEnabled();
final Object advancedProjectionObj = request.getFormatOptions().get(
WMS.ADVANCED_PROJECTION_KEY);
if ((advancedProjectionObj != null) && "false".equalsIgnoreCase(advancedProjectionObj.toString())) {
advancedProjectionHandlingEnabled = false;
continuousMapWrapping = false;
}
final Object mapWrappingObj = request.getFormatOptions().get(
WMS.ADVANCED_PROJECTION_KEY);
if ((mapWrappingObj != null) && "false".equalsIgnoreCase(mapWrappingObj.toString())) {
continuousMapWrapping = false;
}
final List<Interpolation> interpolations = request.getInterpolations();
if ((interpolations == null) || interpolations.isEmpty()) {
interpolationOrdinals = Collections.emptyList();
}
else {
interpolationOrdinals = Lists.transform(
interpolations,
new Function<Interpolation, Integer>() {
@Override
public Integer apply(
final Interpolation input ) {
if (input instanceof InterpolationNearest) {
return Interpolation.INTERP_NEAREST;
}
else if (input instanceof InterpolationNearest) {
return Interpolation.INTERP_NEAREST;
}
else if (input instanceof InterpolationNearest) {
return Interpolation.INTERP_NEAREST;
}
else if (input instanceof InterpolationNearest) {
return Interpolation.INTERP_NEAREST;
}
return Interpolation.INTERP_NEAREST;
}
});
}
}
public int getMaxRenderTime(
final int localMaxRenderTime,
final WMS wms ) {
int wmsMaxRenderTime = wms.getMaxRenderingTime();
if (wmsMaxRenderTime == 0) {
maxRenderTime = localMaxRenderTime;
}
else if (localMaxRenderTime != 0) {
maxRenderTime = Math.min(
wmsMaxRenderTime,
localMaxRenderTime);
}
else {
maxRenderTime = wmsMaxRenderTime;
}
return maxRenderTime;
}
public boolean isOptimizeLineWidth() {
return optimizeLineWidth;
}
public int getMaxErrors() {
return maxErrors;
}
public void setMaxErrors(
final int maxErrors ) {
this.maxErrors = maxErrors;
}
public void setOptimizeLineWidth(
final boolean optimizeLineWidth ) {
this.optimizeLineWidth = optimizeLineWidth;
}
public List<Integer> getInterpolationOrdinals() {
return interpolationOrdinals;
}
public List<Interpolation> getInterpolations() {
if ((interpolationOrdinals != null) && !interpolationOrdinals.isEmpty()) {
return Lists.transform(
interpolationOrdinals,
new Function<Integer, Interpolation>() {
@Override
public Interpolation apply(
final Integer input ) {
return Interpolation.getInstance(input);
}
});
}
return Collections.emptyList();
}
public void setInterpolationOrdinals(
final List<Integer> interpolationOrdinals ) {
this.interpolationOrdinals = interpolationOrdinals;
}
public static boolean isUseGlobalRenderPool() {
return USE_GLOBAL_RENDER_POOL;
}
public Style getStyle() {
return style;
}
public void setStyle(
final Style style ) {
this.style = style;
}
public int getWmsInterpolationOrdinal() {
return wmsIterpolationOrdinal;
}
public void setWmsInterpolationOrdinal(
final int wmsIterpolationOrdinal ) {
this.wmsIterpolationOrdinal = wmsIterpolationOrdinal;
}
public int getMaxRenderTime() {
return maxRenderTime;
}
public void setMaxRenderTime(
final int maxRenderTime ) {
this.maxRenderTime = maxRenderTime;
}
public boolean isRenderScaleMethodAccurate() {
return renderScaleMethodAccurate;
}
public void setRenderScaleMethodAccurate(
final boolean renderScaleMethodAccurate ) {
this.renderScaleMethodAccurate = renderScaleMethodAccurate;
}
public int getBuffer() {
return buffer;
}
public void setBuffer(
final int buffer ) {
this.buffer = buffer;
}
public void setPalette(
final IndexColorModel palette ) {
this.palette = palette;
}
public String getAntialias() {
return antialias;
}
public void setAntialias(
final String antialias ) {
this.antialias = antialias;
}
public boolean isContinuousMapWrapping() {
return continuousMapWrapping;
}
public void setContinuousMapWrapping(
final boolean continuousMapWrapping ) {
this.continuousMapWrapping = continuousMapWrapping;
}
public boolean isAdvancedProjectionHandlingEnabled() {
return advancedProjectionHandlingEnabled;
}
public void setAdvancedProjectionHandlingEnabled(
final boolean advancedProjectionHandlingEnabled ) {
this.advancedProjectionHandlingEnabled = advancedProjectionHandlingEnabled;
}
public boolean isKmlPlacemark() {
return kmlPlacemark;
}
public void setKmlPlacemark(
final boolean kmlPlacemark ) {
this.kmlPlacemark = kmlPlacemark;
}
public boolean isTransparent() {
return transparent;
}
public void setTransparent(
final boolean transparent ) {
this.transparent = transparent;
}
public boolean isMetatile() {
return isMetatile;
}
public void setMetatile(
final boolean isMetatile ) {
this.isMetatile = isMetatile;
}
public Color getBgColor() {
return bgColor;
}
public void setBgColor(
final Color bgColor ) {
this.bgColor = bgColor;
}
public int getMapWidth() {
return mapWidth;
}
public void setMapWidth(
final int mapWidth ) {
this.mapWidth = mapWidth;
}
public int getMapHeight() {
return mapHeight;
}
public void setMapHeight(
final int mapHeight ) {
this.mapHeight = mapHeight;
}
public double getAngle() {
return angle;
}
public void setAngle(
final double angle ) {
this.angle = angle;
}
public int getMaxFilters() {
return maxFilters;
}
public void setMaxFilters(
final int maxFilters ) {
this.maxFilters = maxFilters;
}
public ReferencedEnvelope getEnvelope() {
return envelope;
}
public void setEnvelope(
final ReferencedEnvelope envelope ) {
this.envelope = envelope;
}
public IndexColorModel getPalette() {
return palette;
}
@Override
public byte[] toBinary() {
// combine booleans into a bitset
final BitSet bitSet = new BitSet(
15);
bitSet.set(
0,
continuousMapWrapping);
bitSet.set(
1,
advancedProjectionHandlingEnabled);
bitSet.set(
2,
optimizeLineWidth);
bitSet.set(
3,
transparent);
bitSet.set(
4,
isMetatile);
bitSet.set(
5,
kmlPlacemark);
bitSet.set(
6,
renderScaleMethodAccurate);
boolean storeInterpolationOrdinals = ((interpolationOrdinals != null) && !interpolationOrdinals.isEmpty());
bitSet.set(
7,
storeInterpolationOrdinals);
bitSet.set(
8,
palette != null);
bitSet.set(
9,
maxRenderTime > 0);
bitSet.set(
10,
maxErrors > 0);
bitSet.set(
11,
angle != 0);
bitSet.set(
12,
buffer > 0);
bitSet.set(
13,
bgColor != null);
bitSet.set(
14,
style != null);
final boolean storeCRS = !((envelope.getCoordinateReferenceSystem() == null) || GeoWaveGTDataStore.DEFAULT_CRS
.equals(envelope.getCoordinateReferenceSystem()));
bitSet.set(
15,
storeCRS);
final double minX = envelope.getMinX();
final double minY = envelope.getMinY();
final double maxX = envelope.getMaxX();
final double maxY = envelope.getMaxY();
// required bytes include 32 for envelope doubles,
// 8 for map width and height ints, and 2 for the bitset, and 4 for
// maxFilters
int bufferSize = 46;
final byte[] wktBinary;
if (storeCRS) {
final String wkt = envelope.getCoordinateReferenceSystem().toWKT();
wktBinary = StringUtils.stringToBinary(wkt);
bufferSize += (wktBinary.length + 4);
}
else {
wktBinary = null;
}
if (storeInterpolationOrdinals) {
bufferSize += (4 * interpolationOrdinals.size()) + 4;
}
final byte[] paletteBinary;
if (palette != null) {
final SerializableState serializableColorModel = SerializerFactory.getState(palette);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
final ObjectOutputStream oos = new ObjectOutputStream(
baos);
oos.writeObject(serializableColorModel);
}
catch (final IOException e) {
LOGGER.warn(
"Unable to serialize sample model",
e);
}
paletteBinary = baos.toByteArray();
bufferSize += (paletteBinary.length + 4);
}
else {
paletteBinary = null;
}
if (maxRenderTime > 0) {
bufferSize += 4;
}
if (maxErrors > 0) {
bufferSize += 4;
}
if (angle != 0) {
bufferSize += 8;
}
if (buffer > 0) {
bufferSize += 4;
}
if (bgColor != null) {
bufferSize += 4;
}
final byte[] styleBinary;
if (style != null) {
final SLDTransformer transformer = new SLDTransformer();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
transformer.transform(
new Style[] {
style
},
baos);
}
catch (final TransformerException e) {
LOGGER.warn(
"Unable to create SLD from style",
e);
}
styleBinary = baos.toByteArray();
bufferSize += (styleBinary.length + 4);
}
else {
styleBinary = null;
}
final ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize);
byteBuffer.put(bitSet.toByteArray());
byteBuffer.putDouble(minX);
byteBuffer.putDouble(minY);
byteBuffer.putDouble(maxX);
byteBuffer.putDouble(maxY);
byteBuffer.putInt(mapWidth);
byteBuffer.putInt(mapHeight);
if (wktBinary != null) {
byteBuffer.putInt(wktBinary.length);
byteBuffer.put(wktBinary);
}
if (storeInterpolationOrdinals) {
byteBuffer.putInt(interpolationOrdinals.size());
for (final Integer interpOrd : interpolationOrdinals) {
byteBuffer.putInt(interpOrd);
}
}
if (paletteBinary != null) {
byteBuffer.putInt(paletteBinary.length);
byteBuffer.put(paletteBinary);
}
if (maxRenderTime > 0) {
byteBuffer.putInt(maxRenderTime);
}
if (maxErrors > 0) {
byteBuffer.putInt(maxErrors);
}
if (angle != 0) {
byteBuffer.putDouble(angle);
}
if (buffer > 0) {
byteBuffer.putInt(buffer);
}
if (bgColor != null) {
byteBuffer.putInt(bgColor.getRGB());
}
if (styleBinary != null) {
byteBuffer.putInt(styleBinary.length);
byteBuffer.put(styleBinary);
}
return byteBuffer.array();
}
@Override
public void fromBinary(
final byte[] bytes ) {
final ByteBuffer buf = ByteBuffer.wrap(bytes);
final byte[] bitSetBytes = new byte[2];
buf.get(bitSetBytes);
final BitSet bitSet = BitSet.valueOf(bitSetBytes);
continuousMapWrapping = bitSet.get(0);
advancedProjectionHandlingEnabled = bitSet.get(1);
optimizeLineWidth = bitSet.get(2);
transparent = bitSet.get(3);
isMetatile = bitSet.get(4);
kmlPlacemark = bitSet.get(5);
renderScaleMethodAccurate = bitSet.get(6);
final boolean interpolationOrdinalsStored = bitSet.get(7);
final boolean paletteStored = bitSet.get(8);
final boolean maxRenderTimeStored = bitSet.get(9);
final boolean maxErrorsStored = bitSet.get(10);
final boolean angleStored = bitSet.get(11);
final boolean bufferStored = bitSet.get(12);
final boolean bgColorStored = bitSet.get(13);
final boolean styleStored = bitSet.get(14);
final boolean crsStored = bitSet.get(15);
CoordinateReferenceSystem crs;
final double minX = buf.getDouble();
final double minY = buf.getDouble();
final double maxX = buf.getDouble();
final double maxY = buf.getDouble();
mapWidth = buf.getInt();
mapHeight = buf.getInt();
if (crsStored) {
final byte[] wktBinary = new byte[buf.getInt()];
buf.get(wktBinary);
final String wkt = StringUtils.stringFromBinary(wktBinary);
try {
crs = CRS.parseWKT(wkt);
}
catch (final FactoryException e) {
LOGGER.warn(
"Unable to parse coordinate reference system",
e);
crs = GeoWaveGTDataStore.DEFAULT_CRS;
}
}
else {
crs = GeoWaveGTDataStore.DEFAULT_CRS;
}
envelope = new ReferencedEnvelope(
minX,
maxX,
minY,
maxY,
crs);
if (interpolationOrdinalsStored) {
final int interpolationsLength = buf.getInt();
interpolationOrdinals = new ArrayList<>(
interpolationsLength);
for (int i = 0; i < interpolationsLength; i++) {
interpolationOrdinals.add(buf.getInt());
}
}
else {
interpolationOrdinals = Collections.emptyList();
}
if (paletteStored) {
final byte[] colorModelBinary = new byte[buf.getInt()];
buf.get(colorModelBinary);
try {
final ByteArrayInputStream bais = new ByteArrayInputStream(
colorModelBinary);
final ObjectInputStream ois = new ObjectInputStream(
bais);
final Object o = ois.readObject();
if ((o instanceof SerializableState)
&& (((SerializableState) o).getObject() instanceof IndexColorModel)) {
palette = (IndexColorModel) ((SerializableState) o).getObject();
}
}
catch (final Exception e) {
LOGGER.warn(
"Unable to deserialize color model",
e);
palette = null;
}
}
else {
palette = null;
}
if (maxRenderTimeStored) {
maxRenderTime = buf.getInt();
}
else {
maxRenderTime = 0;
}
if (maxErrorsStored) {
maxErrors = buf.getInt();
}
else {
maxErrors = 0;
}
if (angleStored) {
angle = buf.getDouble();
}
else {
angle = 0;
}
if (bufferStored) {
buffer = buf.getInt();
}
else {
buffer = 0;
}
if (bgColorStored) {
bgColor = new Color(
buf.getInt());
}
else {
bgColor = null;
}
if (styleStored) {
final byte[] styleBinary = new byte[buf.getInt()];
buf.get(styleBinary);
final SLDParser parser = new SLDParser(
CommonFactoryFinder.getStyleFactory(null),
new ByteArrayInputStream(
styleBinary));
final Style[] styles = parser.readXML();
if ((styles != null) && (styles.length > 0)) {
style = styles[0];
}
else {
LOGGER.warn("Unable to deserialize style");
style = null;
}
}
else {
style = null;
}
}
}