/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.mbtiles.gs.wps; import java.awt.Color; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.mbtiles.MBTilesGetMapOutputFormat; import org.geoserver.platform.ServiceException; import org.geoserver.platform.resource.Resource; import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.MapLayerInfo; import org.geoserver.wps.resource.WPSResourceManager; import org.geotools.data.DataUtilities; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.mbtiles.MBTilesFile; import org.geotools.process.ProcessException; import org.geotools.process.factory.DescribeParameter; import org.geotools.process.factory.DescribeProcess; import org.geotools.process.factory.DescribeResult; import org.geotools.process.gs.GSProcess; import org.geotools.referencing.CRS; import org.geotools.styling.Style; import org.geotools.util.logging.Logging; import org.opengis.referencing.crs.CoordinateReferenceSystem; @DescribeProcess(title = "MBTiles", description = "MBTiles Process") public class MBTilesProcess implements GSProcess { public static final String GRIDSET_NAME = "gridset"; public static final String EPSG_900913 = "EPSG:900913"; private static final int TEMP_DIR_ATTEMPTS = 10000; private static final Logger LOGGER = Logging.getLogger(MBTilesProcess.class); /** * GeoServer catalog */ private Catalog catalog; /** * {@link WPSResourceManager} used for cleaning temporary files */ private WPSResourceManager resources; /** * {@link MBTilesGetMapOutputFormat} instance used for creating the MBTiles file */ private MBTilesGetMapOutputFormat mapOutput; public MBTilesProcess(Catalog catalog, MBTilesGetMapOutputFormat mapOutput, WPSResourceManager storage) { this.resources = storage; this.mapOutput = mapOutput; this.catalog = catalog; } public static File createTempDir(File baseDir) { String baseName = System.currentTimeMillis() + "-"; for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { File tempDir = new File(baseDir, baseName + counter); if (tempDir.mkdir()) { return tempDir; } } throw new IllegalStateException("Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')'); } @DescribeResult(name = "mbtile", description = "Link to Compiled MBTiles File") public URL execute( @DescribeParameter(name = "layers", description = "Name of the input layer", collectionType = String.class) Collection<String> layerz, @DescribeParameter(name = "format", description = "Tiles format") String format, @DescribeParameter(name = "boundingbox", description = "Bounding Box of the final MBTile", min = 0) ReferencedEnvelope boundingbox, @DescribeParameter(name = "filename", description = "Name of the .mbtile file", min = 0) String filename, @DescribeParameter(name = "path", description = "Path to the directory where the .mbtile file can be stored ", min = 0) URL path, @DescribeParameter(name = "minZoom", description = "Minimum Zoom level to generate", min = 0) Integer minZoom, @DescribeParameter(name = "maxZoom", description = "Maximum Zoom level to generate", min = 0) Integer maxZoom, @DescribeParameter(name = "minRow", description = "Minimum Row to generate", min = 0) Integer minRow, @DescribeParameter(name = "maxRow", description = "Maximum Row to generate", min = 0) Integer maxRow, @DescribeParameter(name = "minColumn", description = "Minimum Column to generate", min = 0) Integer minColumn, @DescribeParameter(name = "maxColumn", description = "Maximum Column to generate", min = 0) Integer maxColumn, @DescribeParameter(name = "bgColor", description = "Background color", min = 0) String bgColor, @DescribeParameter(name = "transparency", description = "Transparency enabled or not", min = 0, defaultValue = "false") Boolean transparency, @DescribeParameter(name = "styleNames", description = "Name of the styles to use", min = 0, collectionType = String.class) Collection<String> styleNames, @DescribeParameter(name = "stylePath", description = "Path of the style to use", min = 0) URL stylePath, @DescribeParameter(name = "styleBody", description = "Body of the style to use", min = 0) String styleBody) throws IOException { // Extract the filename if present String name; if (filename != null && !filename.isEmpty()) { name = filename; } else if (!layerz.isEmpty()) { String firstLayer = layerz.iterator().next(); name = firstLayer.substring(firstLayer.lastIndexOf(":") + 1); } else { throw new ProcessException("Layers parameter is empty"); } // Initial check on the layers and styleNames size if(styleNames != null && styleNames.size() != layerz.size()){ throw new ProcessException("Layers and styleNames must have the same size"); } // Extract the file path if present final File file; String outputResourceName = name + ".mbtiles"; if (path != null) { File urlToFile = DataUtilities.urlToFile(path); urlToFile.mkdirs(); file = new File(urlToFile, outputResourceName); } else { final Resource resource = resources.getOutputResource(null, outputResourceName); file = resource.file(); } // Create the MBTile file MBTilesFile mbtile = new MBTilesFile(file, true); try { // Initialize the MBTile file in order to avoid exceptions when accessing the geoPackage file mbtile.init(); // Create the GetMap request to use GetMapRequest request = new GetMapRequest(); // Create the layers map for the request ArrayList<MapLayerInfo> layers = new ArrayList<MapLayerInfo>(); // Get the layers from the catalog for (String layername : layerz) { LayerInfo layerInfo = catalog.getLayerByName(layername); // Ensure the Layer is present if (layerInfo == null) { throw new ServiceException("Layer not found: " + layername); } layers.add(new MapLayerInfo(layerInfo)); } request.setLayers(layers); // Generate the bounding box if not present if (boundingbox == null) { try { // generate one from requests layers ReferencedEnvelope bbox = null; for (MapLayerInfo l : request.getLayers()) { ResourceInfo r = l.getResource(); // use native bbox ReferencedEnvelope b = r.getNativeBoundingBox(); if (bbox != null) { // transform b = b.transform(bbox.getCoordinateReferenceSystem(), true); } if (bbox != null) { bbox.include(b); } else { bbox = b; } } request.setBbox(bbox); } catch (Exception e) { String msg = "Must specify bbox, unable to derive from requested layers"; throw new RuntimeException(msg, e); } } else { request.setBbox(boundingbox); } // Extract CRS CoordinateReferenceSystem crs = boundingbox.getCoordinateReferenceSystem(); // Set the request CRS if (crs == null) { // use crs of the layer ResourceInfo r = request.getLayers().iterator().next().getResource(); crs = r.getCRS(); request.setCrs(crs); } else { request.setCrs(crs); } // Set the request SRS request.setSRS(CRS.toSRS(crs)); // Set Background color and Transparency if (bgColor != null && !bgColor.isEmpty()) { request.setBgColor(Color.decode(bgColor)); } request.setTransparent(transparency == null ? false : transparency); // Add a style if (stylePath != null) { request.setStyleUrl(stylePath); } else if (styleBody != null && !styleBody.isEmpty()) { request.setStyleBody(styleBody); } else { request.setStyles(new ArrayList<Style>()); if (styleNames != null && !styleNames.isEmpty()) { for (String styleName : styleNames) { StyleInfo info = catalog.getStyleByName(styleName); if (info != null) { request.getStyles().add(info.getStyle()); } else { request.getStyles().add(null); } } } if (request.getStyles().isEmpty()) { for (MapLayerInfo info : request.getLayers()) { request.getStyles().add(info.getDefaultStyle()); } } } // Set the format of the mbtiles images request.setFormat("none"); Map formatOptions = new HashMap(); formatOptions.put("format", format); // Configure zoom levels if present if (minZoom != null) { formatOptions.put("min_zoom", minZoom); } if (maxZoom != null) { formatOptions.put("max_zoom", maxZoom); } if (minColumn != null) { formatOptions.put("min_column", minColumn); } if (maxColumn != null) { formatOptions.put("max_column", maxColumn); } if (minRow != null) { formatOptions.put("min_row", minRow); } if (maxRow != null) { formatOptions.put("max_row", maxRow); } // Set the gridSet name formatOptions.put(GRIDSET_NAME, EPSG_900913); // Add the format options to the request request.setFormatOptions(formatOptions); // Execute the requests mapOutput.addTiles(mbtile, request, name); } catch (Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } throw new ProcessException(e); } finally { // Close the connection if (mbtile != null) { try { mbtile.close(); } catch (Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } } } } // Add to storage only if it is a temporary file if (path != null) { return DataUtilities.fileToURL(file); } else { return new URL(resources.getOutputResourceUrl(outputResourceName, "application/x-mbtiles")); } } }