/* (c) 2015 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms.utfgrid; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.RenderedImage; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.platform.ServiceException; import org.geoserver.wms.CachedGridReaderLayer; import org.geoserver.wms.MapProducerCapabilities; import org.geoserver.wms.WMS; import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.WebMap; import org.geoserver.wms.map.AbstractMapOutputFormat; import org.geoserver.wms.map.RenderedImageMap; import org.geoserver.wms.map.RenderedImageMapOutputFormat; import org.geotools.map.FeatureLayer; import org.geotools.map.GridCoverageLayer; import org.geotools.map.GridReaderLayer; import org.geotools.map.Layer; import org.geotools.map.RasterLayer; import org.geotools.map.StyleLayer; import org.geotools.map.WMSLayer; import org.geotools.renderer.lite.RendererUtilities; import org.geotools.renderer.lite.StreamingRenderer; import org.geotools.styling.Style; import org.geotools.util.logging.Logging; /** * Handles a GetMap request that spects a map in UTFGrid format. * * @author Andrea Aime - GeoSolutions */ public class UTFGridMapOutputFormat extends AbstractMapOutputFormat { static final Logger LOGGER = Logging.getLogger(UTFGridMapOutputFormat.class); /** the only MIME type this map producer supports */ static final String MIME_TYPE = "application/json;type=utfgrid"; static final String OUTPUT_FORMAT_NAME = "utfgrid"; /** * Default scale-down factor for the output grid size */ static final int DEFAULT_UTFRESOLUTION = 4; /** * Default capabilities for UTFGrid format. * * <p> * <ol> * <li>tiled = unsupported</li> * <li>multipleValues = unsupported</li> * <li>paletteSupported = unsupported</li> * <li>transparency = supported</li> * </ol> */ private static MapProducerCapabilities CAPABILITIES = new MapProducerCapabilities(false, false, false, true, null); private WMS wms; public UTFGridMapOutputFormat(WMS wms) { super(MIME_TYPE, new String[] { OUTPUT_FORMAT_NAME }); this.wms = wms; } public MapProducerCapabilities getCapabilities(String format) { return CAPABILITIES; } @Override public WebMap produceMap(WMSMapContent mapContent) throws ServiceException, IOException { RenderedImageMapOutputFormat of = new RenderedImageMapOutputFormat(wms) { @Override protected StreamingRenderer buildRenderer() { // use a renderer that won't render raster or labels, not even by accident return new PureVectorRenderer(); } @Override protected RenderedImage prepareImage(int width, int height, IndexColorModel palette, boolean transparent) { // each color is a feature index return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } @Override protected void onBeforeRender(StreamingRenderer renderer) { // disable antialiasing, numbers signify ids, we cannot have "half tints" renderer.getJava2DHints().put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); Map hints = renderer.getRendererHints(); double dpi = RendererUtilities.getDpi(hints); dpi = dpi / DEFAULT_UTFRESOLUTION; hints.put(StreamingRenderer.DPI_KEY, dpi); } }; UTFGridEntries entries = new UTFGridEntries(); UTFGridMapContent utfGridMapContent = buildUTFGridMapContent(mapContent, entries); RenderedImageMap map = of.produceMap(utfGridMapContent); return new UTFGridMap(utfGridMapContent, map.getImage()); } private UTFGridMapContent buildUTFGridMapContent(WMSMapContent original, UTFGridEntries entries) { UTFGridColorFunction colorFunction = new UTFGridColorFunction(entries); UTFGridMapContent result = new UTFGridMapContent(original, entries, DEFAULT_UTFRESOLUTION); List<Layer> utfLayers = new ArrayList<>(); for (Layer layer : original.layers()) { // can only draw vector layers, or raster ones that will be transformed into // vectors by a rendering transformation if (!(layer instanceof StyleLayer)) { continue; } StyleLayer sl = (StyleLayer) layer; // deep clone the style and adapt it to use the color function UTFGridStyleVisitor styleVisitor = new UTFGridStyleVisitor(colorFunction); layer.getStyle().accept(styleVisitor); Style copy = (Style) styleVisitor.getCopy(); // if the copy is empty or we don't have vector transformations, skip it if (copy.featureTypeStyles().isEmpty() || (styleVisitor.hasTransformations() && !styleVisitor.hasVectorTransformations())) { continue; } // skip also raster layers not transformed into vector ones if (layer instanceof RasterLayer && !styleVisitor.hasVectorTransformations()) { continue; } if (layer instanceof FeatureLayer) { // copy making sure we retain all attributes FeatureLayer fl = new FeatureLayer( new UTFGridFeatureSource(layer.getFeatureSource(), null), copy); fl.setQuery(layer.getQuery()); sl = fl; } else if(layer instanceof GridCoverageLayer) { GridCoverageLayer gc = (GridCoverageLayer) sl; sl = new GridCoverageLayer(gc.getCoverage(), copy); } else if(layer instanceof WMSLayer) { // these are pure raster, we cannot do a UTFGrid out of them... although // we could try a cascading if the remote server also supports UTFGrid, // in the future continue; } else if(layer instanceof CachedGridReaderLayer) { CachedGridReaderLayer gr = (CachedGridReaderLayer) sl; sl = new CachedGridReaderLayer(gr.getReader(), copy); } else if(layer instanceof GridReaderLayer) { GridReaderLayer gr = (GridReaderLayer) sl; sl = new GridReaderLayer(gr.getReader(), copy); } else { LOGGER.log(Level.WARNING, "Skipping unknown layer " + sl + " of type " + sl.getClass()); continue; } utfLayers.add(sl); } result.layers().addAll(utfLayers); return result; } }