/* (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.geopkg; import static org.geoserver.geopkg.GeoPkg.*; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Logger; import org.geoserver.catalog.ResourceInfo; import org.geoserver.gwc.GWC; import org.geoserver.ows.util.OwsUtils; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.ServiceException; import org.geoserver.tiles.AbstractTilesGetMapOutputFormat; import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.MapLayerInfo; import org.geoserver.wms.RasterCleaner; import org.geoserver.wms.WMS; import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.WebMap; import org.geoserver.wms.WebMapService; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geopkg.GeoPackage; import org.geotools.geopkg.Tile; import org.geotools.geopkg.TileEntry; import org.geotools.geopkg.TileMatrix; import org.geotools.referencing.CRS; import org.geotools.util.logging.Logging; import org.geowebcache.grid.Grid; import org.geowebcache.grid.GridSet; import org.geowebcache.grid.GridSubset; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.google.common.collect.Sets; import com.vividsolutions.jts.geom.Envelope; /** * * WMS GetMap Output Format for GeoPackage * * @author Justin Deoliveira, Boundless * */ public class GeoPackageGetMapOutputFormat extends AbstractTilesGetMapOutputFormat { static Logger LOGGER = Logging.getLogger("org.geoserver.geopkg"); public GeoPackageGetMapOutputFormat(WebMapService webMapService, WMS wms, GWC gwc) { super(MIME_TYPE, "." + EXTENSION, Sets.newHashSet(NAMES), webMapService, wms, gwc); } private static class GeopackageWrapper implements TilesFile { GeoPackage geopkg; TileEntry e; public GeopackageWrapper(GeoPackage geopkg, TileEntry e) throws IOException { this.geopkg = geopkg; this.e = e; } public GeopackageWrapper() throws IOException { this(new GeoPackage(), new TileEntry()); geopkg.init(); } @Override public void setMetadata(String name, ReferencedEnvelope box, String imageFormat, int srid, List<MapLayerInfo> mapLayers, int[] minmax, GridSubset gridSubset) throws IOException, ServiceException { e.setTableName(name); if (mapLayers.size() == 1) { ResourceInfo r = mapLayers.get(0).getResource(); if (e.getIdentifier() == null) { e.setIdentifier(r.getTitle()); } if (e.getDescription() == null){ e.setDescription(r.getAbstract()); } } e.setBounds(box); e.setSrid(srid); GridSet gridSet = gridSubset.getGridSet(); for (int z = minmax[0]; z < minmax[1]; z++) { Grid g = gridSet.getGrid(z); TileMatrix m = new TileMatrix(); m.setZoomLevel(z); m.setMatrixWidth((int) g.getNumTilesWide()); m.setMatrixHeight((int) g.getNumTilesHigh()); m.setTileWidth(gridSubset.getTileWidth()); m.setTileHeight(gridSubset.getTileHeight()); // TODO: not sure about this m.setXPixelSize(g.getResolution()); m.setYPixelSize(g.getResolution()); // m.setXPixelSize(gridSet.getPixelSize()); // m.setYPixelSize(gridSet.getPixelSize()); e.getTileMatricies().add(m); } // figure out the actual bounds of the tiles to be renderered LOGGER.fine("Creating tile entry" + e.getTableName()); geopkg.create(e); } @Override public void addTile(int zoom, int x, int y, byte[] data) throws IOException { Tile t = new Tile(); t.setZoom(zoom); t.setColumn(x); t.setRow(y); t.setData(data); geopkg.add(e, t); } @Override public File getFile() { return geopkg.getFile(); } @Override public void close() { geopkg.close(); } } @Override public WebMap produceMap(WMSMapContent map) throws ServiceException, IOException { /* * From the OGC GeoPackage Specification [1]: * * "The tile coordinate (0,0) always refers to the tile in the upper left corner of the tile matrix at any zoom * level, regardless of the actual availability of that tile" * * This is opposite the default GeoServer grid behavior, so we must always flip the y here. * * [1]: http://www.geopackage.org/spec/#tile_matrix */ map.getRequest().getFormatOptions().put("flipy", "true"); return super.produceMap(map); } @Override protected TilesFile createTilesFile() throws IOException{ return new GeopackageWrapper(); } /** * Add tiles to an existing GeoPackage * * @param geopkg * @param map * @throws IOException */ public void addTiles(GeoPackage geopkg, TileEntry e, GetMapRequest req, String name) throws IOException{ addTiles(new GeopackageWrapper(geopkg,e), req, name); } /** * Special method to add tiles using Geopackage's own grid matrix system rather than GWC gridsubsets * * @param tiles * @param mapLayers * @param map * @throws IOException * @throws ServiceException */ public void addTiles(GeoPackage geopkg, TileEntry e, GetMapRequest request, List<TileMatrix> matrices, String name) throws IOException, ServiceException { List<MapLayerInfo> mapLayers = request.getLayers(); SortedMap<Integer, TileMatrix> matrixSet = new TreeMap<Integer, TileMatrix>(); for (TileMatrix matrix : matrices){ matrixSet.put(matrix.getZoomLevel(), matrix); } if (mapLayers.isEmpty()) { return; } // Get the RasterCleaner object RasterCleaner cleaner = GeoServerExtensions.bean(RasterCleaner.class); // figure out the actual bounds of the tiles to be renderered ReferencedEnvelope bbox = bounds(request); //set metadata e.setTableName(name); e.setBounds(bbox); e.setSrid(srid(request)); e.getTileMatricies().addAll(matrices); LOGGER.fine("Creating tile entry" + e.getTableName()); geopkg.create(e); GetMapRequest req = new GetMapRequest(); OwsUtils.copy(request, req, GetMapRequest.class); req.setLayers(mapLayers); Map formatOpts = req.getFormatOptions(); Integer minZoom = null; if (formatOpts.containsKey("min_zoom")) { minZoom = Integer.parseInt(formatOpts.get("min_zoom").toString()); } Integer maxZoom = null; if (formatOpts.containsKey("max_zoom")) { maxZoom = Integer.parseInt(formatOpts.get("max_zoom").toString()); } else if (formatOpts.containsKey("num_zooms")) { maxZoom = minZoom + Integer.parseInt(formatOpts.get("num_zooms").toString()); } if (minZoom != null || maxZoom != null) { matrixSet = matrixSet.subMap(minZoom, maxZoom); } String imageFormat = formatOpts.containsKey("format") ? parseFormatFromOpts(formatOpts) : findBestFormat(request); CoordinateReferenceSystem crs = getCoordinateReferenceSystem(request); if (crs==null) { String srs = getSRS(request); try { crs = CRS.decode(srs); } catch (Exception ex) { throw new ServiceException(ex); } } double xSpan = crs.getCoordinateSystem().getAxis(0).getMaximumValue() - crs.getCoordinateSystem().getAxis(0).getMinimumValue(); double ySpan = crs.getCoordinateSystem().getAxis(1).getMaximumValue() - crs.getCoordinateSystem().getAxis(1).getMinimumValue(); double xOffset = crs.getCoordinateSystem().getAxis(0).getMinimumValue(); double yOffset = crs.getCoordinateSystem().getAxis(1).getMinimumValue(); req.setFormat(imageFormat); req.setCrs(crs); //column and row bounds Integer minColumn = null, maxColumn = null, minRow = null, maxRow = null; if (formatOpts.containsKey("min_column")) { minColumn = Integer.parseInt(formatOpts.get("min_column").toString()); } if (formatOpts.containsKey("max_column")) { maxColumn = Integer.parseInt(formatOpts.get("max_column").toString()); } if (formatOpts.containsKey("min_row")) { minRow = Integer.parseInt(formatOpts.get("min_row").toString()); } if (formatOpts.containsKey("max_row")) { maxRow = Integer.parseInt(formatOpts.get("max_row").toString()); } for (TileMatrix matrix : matrixSet.values()) { req.setWidth(matrix.getTileWidth()); req.setHeight(matrix.getTileHeight()); //long[] intersect = gridSubset.getCoverageIntersection(z, bbox); double resX = xSpan / matrix.getMatrixWidth(); double resY = ySpan / matrix.getMatrixHeight(); long minX = Math.round(Math.floor((bbox.getMinX()-xOffset) / resX)); long minY = Math.round(Math.floor((bbox.getMinY()-yOffset) / resY)); long maxX = Math.round(Math.ceil((bbox.getMaxX()-xOffset) / resX)); long maxY = Math.round(Math.ceil((bbox.getMaxY()-yOffset) / resY)); minX = minColumn == null? minX : Math.max(minColumn, minX); maxX = maxColumn == null? maxX : Math.min(maxColumn, maxX); minY = minRow == null? minY : Math.max(minRow, minY); maxY = maxRow == null? maxY : Math.min(maxRow, maxY); for (long x = minX; x < maxX; x++) { for (long y = minY; y < maxY; y++) { req.setBbox(new Envelope( xOffset + x * resX , xOffset + (x+1) * resX, yOffset + y * resY, yOffset + (y+1) * resY)); WebMap result = webMapService.getMap(req); Tile t = new Tile(); t.setZoom(matrix.getZoomLevel()); t.setColumn((int) x); t.setRow((int) y); t.setData(toBytes(result)); geopkg.add(e, t); // Cleanup cleaner.finished(null); } } } } }