Java tutorial
/* * Copyright 2009-2016 DigitalGlobe, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. * */ /** * */ package org.mrgeo.resources.tms; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.mrgeo.colorscale.ColorScale; import org.mrgeo.colorscale.ColorScaleManager; import org.mrgeo.colorscale.applier.ColorScaleApplier; import org.mrgeo.core.MrGeoConstants; import org.mrgeo.data.ProviderProperties; import org.mrgeo.data.raster.RasterUtils; import org.mrgeo.data.tile.TileNotFoundException; import org.mrgeo.image.MrsImageException; import org.mrgeo.image.MrsPyramidMetadata; import org.mrgeo.services.Configuration; import org.mrgeo.services.SecurityUtils; import org.mrgeo.services.mrspyramid.rendering.ImageHandlerFactory; import org.mrgeo.services.mrspyramid.rendering.ImageRenderer; import org.mrgeo.services.mrspyramid.rendering.ImageResponseWriter; import org.mrgeo.services.mrspyramid.rendering.TiffImageRenderer; import org.mrgeo.services.tms.TmsService; import org.mrgeo.utils.HadoopUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Attr; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.activation.MimetypesFileTypeMap; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMSource; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutionException; /** * */ @Path("/tms") public class TileMapServiceResource { private static final Logger log = LoggerFactory.getLogger(TileMapServiceResource.class); private static final MimetypesFileTypeMap mimeTypeMap = new MimetypesFileTypeMap(); private static final String VERSION = "1.0.0"; private static final String SRS = "EPSG:4326"; private static final String GENERAL_ERROR = "An error occurred in Tile Map Service"; private static String imageBaseDir = HadoopUtils.getDefaultImageBaseDirectory(); //public static String KML_VERSION = "http://www.opengis.net/kml/2.2"; //public static String KML_EXTENSIONS = "http://www.google.com/kml/ext/2.2"; //public static String KML_MIME_TYPE = "application/vnd.google-earth.kml+xml"; @Context TmsService service; static Properties props; static { init(); } public static void init() { try { if (props == null) { props = Configuration.getInstance().getProperties(); } } catch (final IllegalStateException e) { log.error(MrGeoConstants.MRGEO_HDFS_IMAGE + " must be specified in the MrGeo configuration file (" + e.getMessage() + ")"); } } protected static Response createEmptyTile(final ImageResponseWriter writer, final int width, final int height) { // return an empty image final int dataType; if (writer.getResponseMimeType().equals("image/jpeg")) { dataType = BufferedImage.TYPE_3BYTE_BGR; } else { // dataType = BufferedImage.TYPE_INT_ARGB; dataType = BufferedImage.TYPE_4BYTE_ABGR; } final BufferedImage bufImg = new BufferedImage(width, height, dataType); final Graphics2D g = bufImg.createGraphics(); g.setColor(new Color(0, 0, 0, 0)); g.fillRect(0, 0, width, height); g.dispose(); return writer.write(bufImg.getData()).build(); } protected static Document mrsPyramidMetadataToTileMapXml(final String raster, final String url, final MrsPyramidMetadata mpm) throws ParserConfigurationException { /* * String tileMap = "<?xml version='1.0' encoding='UTF-8' ?>" + * "<TileMap version='1.0.0' tilemapservice='http://localhost/mrgeo-services/api/tms/1.0.0'>" + * " <Title>AfPk Elevation V2</Title>" + " <Abstract>A test of V2 MrsPyramid.</Abstract>" * + " <SRS>EPSG:4326</SRS>" + " <BoundingBox minx='68' miny='33' maxx='72' maxy='35' />" + * " <Origin x='68' y='33' />" + * " <TileFormat width='512' height='512' mime-type='image/tiff' extension='tif' />" + * " <TileSets profile='global-geodetic'>" + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/1' units-per-pixel='0.3515625' order='1' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/2' units-per-pixel='0.17578125' order='2' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/3' units-per-pixel='0.08789063' order='3' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/4' units-per-pixel='0.08789063' order='4' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/5' units-per-pixel='0.08789063' order='5' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/6' units-per-pixel='0.08789063' order='6' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/7' units-per-pixel='0.08789063' order='7' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/8' units-per-pixel='0.08789063' order='8' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/9' units-per-pixel='0.08789063' order='9' />" * + * " <TileSet href='http://localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2/10' units-per-pixel='0.08789063' order='10' />" * + " </TileSets>" + "</TileMap>"; */ final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); // root elements final Document doc = docBuilder.newDocument(); final Element rootElement = doc.createElement("TileMap"); doc.appendChild(rootElement); final Attr v = doc.createAttribute("version"); v.setValue(VERSION); rootElement.setAttributeNode(v); final Attr tilemapservice = doc.createAttribute("tilemapservice"); tilemapservice.setValue(normalizeUrl(normalizeUrl(url).replace(raster, ""))); rootElement.setAttributeNode(tilemapservice); // child elements final Element title = doc.createElement("Title"); title.setTextContent(raster); rootElement.appendChild(title); final Element abst = doc.createElement("Abstract"); abst.setTextContent(""); rootElement.appendChild(abst); final Element srs = doc.createElement("SRS"); srs.setTextContent(SRS); rootElement.appendChild(srs); final Element bbox = doc.createElement("BoundingBox"); rootElement.appendChild(bbox); final Attr minx = doc.createAttribute("minx"); minx.setValue(String.valueOf(mpm.getBounds().w)); bbox.setAttributeNode(minx); final Attr miny = doc.createAttribute("miny"); miny.setValue(String.valueOf(mpm.getBounds().s)); bbox.setAttributeNode(miny); final Attr maxx = doc.createAttribute("maxx"); maxx.setValue(String.valueOf(mpm.getBounds().e)); bbox.setAttributeNode(maxx); final Attr maxy = doc.createAttribute("maxy"); maxy.setValue(String.valueOf(mpm.getBounds().n)); bbox.setAttributeNode(maxy); final Element origin = doc.createElement("Origin"); rootElement.appendChild(origin); final Attr x = doc.createAttribute("x"); x.setValue(String.valueOf(mpm.getBounds().w)); origin.setAttributeNode(x); final Attr y = doc.createAttribute("y"); y.setValue(String.valueOf(mpm.getBounds().s)); origin.setAttributeNode(y); final Element tileformat = doc.createElement("TileFormat"); rootElement.appendChild(tileformat); final Attr w = doc.createAttribute("width"); w.setValue(String.valueOf(mpm.getTilesize())); tileformat.setAttributeNode(w); final Attr h = doc.createAttribute("height"); h.setValue(String.valueOf(mpm.getTilesize())); tileformat.setAttributeNode(h); final Attr mt = doc.createAttribute("mime-type"); mt.setValue("image/tiff"); tileformat.setAttributeNode(mt); final Attr ext = doc.createAttribute("extension"); ext.setValue("tif"); tileformat.setAttributeNode(ext); final Element tilesets = doc.createElement("TileSets"); rootElement.appendChild(tilesets); final Attr profile = doc.createAttribute("profile"); profile.setValue("global-geodetic"); tilesets.setAttributeNode(profile); for (int i = 0; i <= mpm.getMaxZoomLevel(); i++) { final Element tileset = doc.createElement("TileSet"); tilesets.appendChild(tileset); final Attr href = doc.createAttribute("href"); href.setValue(normalizeUrl(normalizeUrl(url)) + "/" + i); tileset.setAttributeNode(href); final Attr upp = doc.createAttribute("units-per-pixel"); upp.setValue(String.valueOf(180d / 256d / Math.pow(2, i))); tileset.setAttributeNode(upp); final Attr order = doc.createAttribute("order"); order.setValue(String.valueOf(i)); tileset.setAttributeNode(order); } return doc; } protected static Document mrsPyramidToTileMapServiceXml(final String url, final List<String> pyramidNames) throws ParserConfigurationException, DOMException, UnsupportedEncodingException { /* * String tileMapService = "<?xml version='1.0' encoding='UTF-8' ?>" + * "<TileMapService version='1.0.0' services='http://localhost/mrgeo-services/api/tms/'>" + * " <Title>Example Tile Map Service</Title>" + * " <Abstract>This is a longer description of the example tiling map service.</Abstract>" + * " <TileMaps>" + " <TileMap " + " title='AfPk Elevation V2' " + * " srs='EPSG:4326' " + " profile='global-geodetic' " + * " href='http:///localhost/mrgeo-services/api/tms/1.0.0/AfPkElevationV2' />" + * " </TileMaps>" + "</TileMapService>"; */ final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); // root elements final Document doc = docBuilder.newDocument(); final Element rootElement = doc.createElement("TileMapService"); doc.appendChild(rootElement); final Attr v = doc.createAttribute("version"); v.setValue(VERSION); rootElement.setAttributeNode(v); final Attr service = doc.createAttribute("services"); service.setValue(normalizeUrl(normalizeUrl(url).replace(VERSION, ""))); rootElement.setAttributeNode(service); // child elements final Element title = doc.createElement("Title"); title.setTextContent("Tile Map Service"); rootElement.appendChild(title); final Element abst = doc.createElement("Abstract"); abst.setTextContent("MrGeo MrsPyramid rasters available as TMS"); rootElement.appendChild(abst); final Element tilesets = doc.createElement("TileMaps"); rootElement.appendChild(tilesets); Collections.sort(pyramidNames); for (final String p : pyramidNames) { final Element tileset = doc.createElement("TileMap"); tilesets.appendChild(tileset); final Attr href = doc.createAttribute("href"); href.setValue(normalizeUrl(url) + "/" + URLEncoder.encode(p, "UTF-8")); tileset.setAttributeNode(href); final Attr maptitle = doc.createAttribute("title"); maptitle.setValue(p); tileset.setAttributeNode(maptitle); final Attr srs = doc.createAttribute("srs"); srs.setValue(SRS); tileset.setAttributeNode(srs); final Attr profile = doc.createAttribute("profile"); profile.setValue("global-geodetic"); tileset.setAttributeNode(profile); } return doc; } protected static String normalizeUrl(final String url) { String newUrl; newUrl = (url.lastIndexOf("/") == url.length() - 1) ? url.substring(0, url.length() - 1) : url; return newUrl; } protected static Document rootResourceXml(final String url) throws ParserConfigurationException { /* * <?xml version="1.0" encoding="UTF-8" ?> <Services> <TileMapService * title="MrGeo Tile Map Service" version="1.0.0" * href="http://localhost:8080/mrgeo-services/api/tms/1.0.0" /> </Services> */ final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); final Document doc = docBuilder.newDocument(); final Element rootElement = doc.createElement("Services"); doc.appendChild(rootElement); final Element tms = doc.createElement("TileMapService"); rootElement.appendChild(tms); final Attr title = doc.createAttribute("title"); title.setValue("MrGeo Tile Map Service"); tms.setAttributeNode(title); final Attr v = doc.createAttribute("version"); v.setValue(VERSION); tms.setAttributeNode(v); final Attr href = doc.createAttribute("href"); href.setValue(normalizeUrl(url) + "/" + VERSION); tms.setAttributeNode(href); return doc; } @GET @Produces("text/xml") public Response getRootResource(@Context final HttpServletRequest hsr) { try { final String url = hsr.getRequestURL().toString(); final Document doc = rootResourceXml(url); final DOMSource source = new DOMSource(doc); return Response.ok(source, "text/xml").header("Content-type", "text/xml").build(); } catch (final ParserConfigurationException ex) { return Response.status(Status.INTERNAL_SERVER_ERROR).entity(GENERAL_ERROR).build(); } } @SuppressWarnings("static-method") @GET @Produces("image/*") @Path("{version}/{raster}/{z}/{x}/{y}.{format}") public Response getTile(@PathParam("version") final String version, @PathParam("raster") String pyramid, @PathParam("z") final Integer z, @PathParam("x") final Integer x, @PathParam("y") final Integer y, @PathParam("format") final String format, @QueryParam("color-scale-name") final String colorScaleName, @QueryParam("color-scale") final String colorScale, @QueryParam("min") final Double min, @QueryParam("max") final Double max, @DefaultValue("1") @QueryParam("maskMax") final Double maskMax, @QueryParam("mask") final String mask) { final ImageRenderer renderer; Raster raster; try { renderer = (ImageRenderer) ImageHandlerFactory.getHandler(format, ImageRenderer.class); // TODO: Need to construct provider properties from the WebRequest using // a new security layer and pass those properties. // Apply mask if requested ProviderProperties providerProperties = SecurityUtils.getProviderProperties(); if (mask != null && !mask.isEmpty()) { raster = renderer.renderImage(pyramid, x, y, z, mask, maskMax, providerProperties); } else { raster = renderer.renderImage(pyramid, x, y, z, providerProperties); } if (!(renderer instanceof TiffImageRenderer) && raster.getNumBands() != 3 && raster.getNumBands() != 4) { ColorScale cs = null; if (colorScaleName != null) { cs = ColorScaleManager.fromName(colorScaleName, props); } else if (colorScale != null) { cs = ColorScaleManager.fromJSON(colorScale); } // else // { // cs = ColorScaleManager.fromPyramid(pyramid, driver); // } final double[] extrema = renderer.getExtrema(); // Check for min/max override values from the request if (min != null) { extrema[0] = min; } if (max != null) { extrema[1] = max; } raster = ((ColorScaleApplier) ImageHandlerFactory.getHandler(format, ColorScaleApplier.class)) .applyColorScale(raster, cs, extrema, renderer.getDefaultValues()); } // Apply mask if requested // if (mask != null && !mask.isEmpty()) // { // try // { // final MrsImagePyramidMetadata maskMetadata = service.getMetadata(mask); // // final Raster maskRaster = renderer.renderImage(mask, x, y, z, props, driver); // final WritableRaster wr = RasterUtils.makeRasterWritable(raster); // // final int band = 0; // final double nodata = maskMetadata.getDefaultValue(band); // // for (int w = 0; w < maskRaster.getWidth(); w++) // { // for (int h = 0; h < maskRaster.getHeight(); h++) // { // final double maskPixel = maskRaster.getSampleDouble(w, h, band); // if (maskPixel > maskMax || Double.compare(maskPixel, nodata) == 0) // { // wr.setSample(w, h, band, nodata); // } // } // } // } // catch (final TileNotFoundException ex) // { // raster = RasterUtils.createEmptyRaster(raster.getWidth(), raster.getHeight(), raster // .getNumBands(), raster.getTransferType(), 0); // } // } return ((ImageResponseWriter) ImageHandlerFactory.getHandler(format, ImageResponseWriter.class)) .write(raster, renderer.getDefaultValues()).build(); } catch (final IllegalArgumentException e) { return Response.status(Status.BAD_REQUEST).entity("Unsupported image format - " + format).build(); } catch (final IOException e) { return Response.status(Status.NOT_FOUND).entity("Tile map not found - " + pyramid).build(); } catch (final MrsImageException e) { return Response.status(Status.NOT_FOUND).entity("Tile map not found - " + pyramid + ": " + z).build(); } catch (final TileNotFoundException e) { // return Response.status(Status.NOT_FOUND).entity("Tile not found").build(); try { final MrsPyramidMetadata metadata = service.getMetadata(pyramid); return createEmptyTile( ((ImageResponseWriter) ImageHandlerFactory.getHandler(format, ImageResponseWriter.class)), metadata.getTilesize(), metadata.getTilesize()); } catch (final Exception e1) { log.error("Exception occurred creating blank tile " + pyramid + "/" + z + "/" + x + "/" + y + "." + format, e1); } } catch (final ColorScale.BadJSONException e) { return Response.status(Status.NOT_FOUND).entity("Unable to parse color scale JSON").build(); } catch (final ColorScale.BadSourceException e) { return Response.status(Status.NOT_FOUND).entity("Unable to open color scale file").build(); } catch (final ColorScale.BadXMLException e) { return Response.status(Status.NOT_FOUND).entity("Unable to parse color scale XML").build(); } catch (final ColorScale.ColorScaleException e) { return Response.status(Status.NOT_FOUND).entity("Unable to open color scale").build(); } catch (final Exception e) { log.error("Exception occurred getting tile " + pyramid + "/" + z + "/" + x + "/" + y + "." + format, e); } return Response.status(Status.INTERNAL_SERVER_ERROR).entity(GENERAL_ERROR).build(); } @GET @Produces("text/xml") @Path("/{version}/{raster}") public Response getTileMap(@PathParam("version") final String version, @PathParam("raster") String raster, @Context final HttpServletRequest hsr) { try { final String url = hsr.getRequestURL().toString(); // Check cache for metadata, if not found read from pyramid // and store in cache final MrsPyramidMetadata mpm = service.getMetadata(raster); final Document doc = mrsPyramidMetadataToTileMapXml(raster, url, mpm); final DOMSource source = new DOMSource(doc); return Response.ok(source, "text/xml").header("Content-type", "text/xml").build(); } catch (final ExecutionException e) { log.error("MrsPyramid " + raster + " not found", e); return Response.status(Status.NOT_FOUND).entity("Tile map not found - " + raster).build(); } catch (final ParserConfigurationException ex) { return Response.status(Status.INTERNAL_SERVER_ERROR).entity(GENERAL_ERROR).build(); } } @GET @Produces("text/xml") @Path("/{version}") public Response getTileMapService(@PathParam("version") final String version, @Context final HttpServletRequest hsr) { try { final String url = hsr.getRequestURL().toString(); final Document doc = mrsPyramidToTileMapServiceXml(url, service.listImages()); final DOMSource source = new DOMSource(doc); return Response.ok(source, "text/xml").header("Content-type", "text/xml").build(); } catch (final IOException e) { log.error("File system exception for " + imageBaseDir, e); return Response.status(Status.INTERNAL_SERVER_ERROR).entity(GENERAL_ERROR).build(); } catch (final ParserConfigurationException ex) { return Response.status(Status.INTERNAL_SERVER_ERROR).entity(GENERAL_ERROR).build(); } } protected Response returnEmptyTile(final int width, final int height, final String format) throws Exception { //return an empty image ImageResponseWriter writer = (ImageResponseWriter) ImageHandlerFactory.getHandler(format, ImageResponseWriter.class); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bands; Double[] nodatas; if (format.equalsIgnoreCase("jpg") || format.equalsIgnoreCase("jpeg")) { bands = 3; nodatas = new Double[] { 0.0, 0.0, 0.0 }; } else { bands = 4; nodatas = new Double[] { 0.0, 0.0, 0.0, 0.0 }; } Raster raster = RasterUtils.createEmptyRaster(width, height, bands, DataBuffer.TYPE_BYTE, nodatas); writer.writeToStream(raster, ArrayUtils.toPrimitive(nodatas), baos); byte[] imageData = baos.toByteArray(); IOUtils.closeQuietly(baos); final String type = mimeTypeMap.getContentType("output." + format); return Response.ok(imageData).header("Content-Type", type).build(); // A 404 - Not Found response may be the most appropriate, but results in pink tiles, // maybe change that behavior on the OpenLayers client? // return Response.status( Response.Status.NOT_FOUND).build(); } }