package mil.nga.giat.geowave.adapter.vector.render; import java.awt.geom.Point2D; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.geoserver.wms.DefaultWebMapService; import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.ScaleComputationMethod; import org.geoserver.wms.WMSMapContent; import org.geotools.map.FeatureLayer; import org.geotools.map.MapViewport; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.aol.cyclops.data.async.Queue; import mil.nga.giat.geowave.core.store.query.aggregate.Aggregation; public class DistributedRenderAggregation implements Aggregation<DistributedRenderOptions, DistributedRenderResult, SimpleFeature> { private final static Logger LOGGER = LoggerFactory.getLogger(DistributedRenderAggregation.class); private DistributedRenderMapOutputFormat currentRenderer; private DistributedRenderResult currentResult; // use a cyclops-react queue to feed simple features asynchronously while a // render thread consumes the features private Queue<SimpleFeature> queue; private CompletableFuture<DistributedRenderResult> asyncRenderer; private DistributedRenderOptions options; protected DistributedRenderAggregation() {} public DistributedRenderAggregation( final DistributedRenderOptions options ) { this.options = options; } @Override public DistributedRenderOptions getParameters() { return options; } @Override public void setParameters( final DistributedRenderOptions options ) { this.options = options; } private void initRenderer( final SimpleFeatureType type ) { currentRenderer = new DistributedRenderMapOutputFormat( options); final WMSMapContent mapContent = new WMSMapContent(); final GetMapRequest request = new GetMapRequest(); mapContent.setBgColor( options.getBgColor()); request.setBgColor( options.getBgColor()); mapContent.setPalette( options.getPalette()); request.setPalette( options.getPalette()); mapContent.setAngle( options.getAngle()); request.setAngle( options.getAngle()); mapContent.setBuffer( options.getBuffer()); request.setBuffer( options.getBuffer()); mapContent.setMapWidth( options.getMapWidth()); request.setWidth( options.getMapWidth()); mapContent.setMapHeight( options.getMapHeight()); request.setHeight( options.getMapHeight()); mapContent.setTransparent( options.isTransparent()); request.setTransparent( options.isTransparent()); mapContent.setViewport( new MapViewport( options.getEnvelope())); request.setBbox( options.getEnvelope()); request.setInterpolations( options.getInterpolations()); final Map formatOptions = new HashMap<>(); formatOptions.put( "antialias", options.getAntialias()); formatOptions.put( "timeout", options.getMaxRenderTime()); formatOptions.put( "kmplacemark", Boolean.valueOf( options.isKmlPlacemark())); // this sets a static variable, but its the only method available // (multiple geoserver clients with different settings hitting the same // distributed backend, may conflict on these settings) // we get around this by overriding these settings on the renderHints // object within DistributedRenderer so it is no longer using these // static settings, but these static properties must be set to avoid // NPEs System.setProperty( "OPTIMIZE_LINE_WIDTH", Boolean.toString( options.isOptimizeLineWidth())); System.setProperty( "MAX_FILTER_RULES", Integer.toString( options.getMaxFilters())); System.setProperty( "USE_GLOBAL_RENDERING_POOL", Boolean.toString( DistributedRenderOptions.isUseGlobalRenderPool())); new DefaultWebMapService(null).setApplicationContext(null); request.setFormatOptions( formatOptions); request.setWidth( options.getMapWidth()); request.setHeight( options.getMapHeight()); request.setTiled( options.isMetatile()); request.setScaleMethod( options.isRenderScaleMethodAccurate() ? ScaleComputationMethod.Accurate : ScaleComputationMethod.OGC); if (options.isMetatile()) { // it doesn't matter what this is, as long as its not null, we are // just ensuring proper transparency usage based on meta-tiling // rules request.setTilesOrigin( new Point2D.Double()); } mapContent.setRequest( request); queue = new Queue<>(); mapContent.addLayer( new FeatureLayer( new AsyncQueueFeatureCollection( type, queue), options.getStyle())); // produce map in a separate thread... asyncRenderer = CompletableFuture.supplyAsync( () -> { currentRenderer.produceMap( mapContent).dispose(); return currentRenderer.getDistributedRenderResult(); }); } @Override public DistributedRenderResult getResult() { if ((queue != null) && (asyncRenderer != null)) { queue.close(); DistributedRenderResult result = null; // may not need to do this, waiting on map production may be // sufficient try { if (options.getMaxRenderTime() > 0) { result = asyncRenderer.get( options.getMaxRenderTime(), TimeUnit.SECONDS); } else { result = asyncRenderer.get(); } } catch (InterruptedException | ExecutionException | TimeoutException e) { LOGGER.warn( "Unable to get distributed render result", e); } currentResult = result; clearRenderer(); } return currentResult; } @Override public void clearResult() { stopRenderer(); clearRenderer(); currentResult = null; } public void stopRenderer() { if (currentRenderer != null) { currentRenderer.stopRendering(); } if (asyncRenderer != null) { asyncRenderer.cancel(true); } } public void clearRenderer() { queue = null; currentRenderer = null; asyncRenderer = null; } private synchronized void ensureOpen( final SimpleFeatureType type ) { if (currentRenderer == null) { initRenderer(type); } } @Override public void aggregate( final SimpleFeature entry ) { ensureOpen(entry.getFeatureType()); queue.add(entry); } }