org.mrgeo.services.wms.WmsGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.mrgeo.services.wms.WmsGenerator.java

Source

/*
 * Copyright 2009-2014 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.services.wms;

import java.awt.image.Raster;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.Semaphore;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.apache.commons.lang.StringUtils;
import org.mrgeo.core.MrGeoProperties;
import org.mrgeo.data.DataProviderFactory;
import org.mrgeo.data.image.MrsImageDataProvider;
import org.mrgeo.image.MrsImage;
import org.mrgeo.image.MrsImagePyramid;
import org.mrgeo.rasterops.ColorScale;
import org.mrgeo.rasterops.ColorScale.ColorScaleException;
import org.mrgeo.rasterops.OpImageRegistrar;
import org.mrgeo.services.SecurityUtils;
import org.mrgeo.services.ServletUtils;
import org.mrgeo.services.Version;
import org.mrgeo.services.mrspyramid.ColorScaleManager;
import org.mrgeo.services.mrspyramid.rendering.ColorScaleApplier;
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.utils.RequestUtils;
import org.mrgeo.utils.Bounds;
import org.mrgeo.utils.LatLng;
import org.mrgeo.utils.TMSUtils;
import org.mrgeo.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

//import org.mrgeo.utils.LoggingUtils;

/**
 * OGC WMS implementation - See https://107.23.31.196/redmine/projects/mrgeo/wiki/WmsReference for
 * details.
 */
public class WmsGenerator extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static final Logger log = LoggerFactory.getLogger(WmsGenerator.class);

    //  private static Path basePath = null;
    //  private static Path colorScaleBasePath = null;
    private static Semaphore semaphore = null;

    // private static CoordinateReferenceSystem coordSys = null;

    public static final String JPEG_MIME_TYPE = "image/jpeg";
    public static final String PNG_MIME_TYPE = "image/png";
    public static final String TIFF_MIME_TYPE = "image/tiff";

    private Version version = null;
    public static final String WMS_VERSION = "1.3.0";

    public WmsGenerator() throws NumberFormatException {
        super();
        if (semaphore == null) {
            semaphore = new Semaphore(Integer.parseInt(
                    MrGeoProperties.getInstance().getProperty("org.mrgeo.services.WmsGenerator.permits", "3")),
                    true);
        }
    }

    /**
     * Calculates the scale of the requested WMS layer
     *
     * @param image
     *          source data
     * @return scale resolution
     */
    public static double calculateScale(final MrsImage image) {
        // WMS defines a pixel as .28mm
        final double h = TMSUtils.resolution(image.getZoomlevel(), image.getTilesize()) * LatLng.METERS_PER_DEGREE;
        return h / 2.8e-4;
    }

    /*
     * GetMap implementation
     */
    private static void getMap(final HttpServletRequest request, final HttpServletResponse response,
            final Properties providerProperties) throws Exception {
        //    final Path filePath = getLayerPath(ServletUtils.validateAndGetParamValue(request, "layers",
        //        "string"));
        final String layer = ServletUtils.validateAndGetParamValue(request, "layers", "string");
        final int width = Integer.valueOf(ServletUtils.validateAndGetParamValue(request, "width", "integer"));
        final int height = Integer.valueOf(ServletUtils.validateAndGetParamValue(request, "height", "integer"));
        Bounds bounds = RequestUtils
                .boundsFromParam(ServletUtils.validateAndGetParamValue(request, "bbox", "string"));

        final String style = ServletUtils.getParamValue(request, "style");
        final String srs = ServletUtils.getParamValue(request, "srs");

        final String imageFormat = ServletUtils.getParamValue(request, "format");

        final ImageRenderer renderer = (ImageRenderer) ImageHandlerFactory.getHandler(imageFormat,
                ImageRenderer.class);

        //Reproject bounds to EPSG:4326 if necessary
        bounds = RequestUtils.reprojectBounds(bounds, srs);

        Raster result = renderer.renderImage(layer, bounds, width, height, providerProperties, srs);

        result = colorRaster(layer, style, imageFormat, renderer, result);

        ((ImageResponseWriter) ImageHandlerFactory.getHandler(imageFormat, ImageResponseWriter.class)).write(result,
                layer, bounds, response);
    }

    /*
     * GetMosaic implementation
     */
    private static void getMosaic(final HttpServletRequest request, final HttpServletResponse response,
            final Properties providerProperties) throws Exception {
        final String layer = ServletUtils.validateAndGetParamValue(request, "layers", "string");
        final Bounds bounds = RequestUtils
                .boundsFromParam(ServletUtils.validateAndGetParamValue(request, "bbox", "string"));
        final String style = ServletUtils.getParamValue(request, "style");
        final String srs = ServletUtils.getParamValue(request, "srs");

        final String imageFormat = ServletUtils.getParamValue(request, "format");

        final ImageRenderer renderer = (ImageRenderer) ImageHandlerFactory.getHandler(imageFormat,
                ImageRenderer.class);
        Raster result = renderer.renderImage(layer, bounds, providerProperties, srs);

        result = colorRaster(layer, style, imageFormat, renderer, result);

        ((ImageResponseWriter) ImageHandlerFactory.getHandler(imageFormat, ImageResponseWriter.class)).write(result,
                layer, bounds, response);
    }

    private static ColorScale getDefaultColorScale() {
        ColorScale cs = null;
        try {
            cs = ColorScaleManager.fromName("Default");
        } catch (ColorScaleException e) {
            // Do nothing - there may not be a Default color scale defined
        }
        if (cs == null) {
            cs = ColorScale.createDefault();
        }
        return cs;
    }

    /*
     * Returns a list of all MrsImagePyramid version 2 data in the home data directory
     */
    private static MrsImageDataProvider[] getPyramidFilesList(final Properties providerProperties)
            throws IOException {
        String[] images = DataProviderFactory.listImages(providerProperties);

        Arrays.sort(images);

        MrsImageDataProvider[] providers = new MrsImageDataProvider[images.length];

        for (int i = 0; i < images.length; i++) {
            providers[i] = DataProviderFactory.getMrsImageDataProvider(images[i],
                    DataProviderFactory.AccessMode.READ, providerProperties);
        }

        return providers;
        //    Path basePath = new Path(HadoopUtils.getDefaultImageBaseDirectory());
        //    final FileSystem fileSystem = HadoopFileUtils.getFileSystem(basePath);
        //    // log.debug(HadoopFileUtils.getDefaultRoot().toString());
        //    // log.debug("basePath: {}", fileSystem.makeQualified(basePath).toString());
        //    FileStatus[] allFiles = fileSystem.listStatus(basePath);
        //    if (allFiles == null || allFiles.length == 0)
        //    {
        //      log.warn("Base path either doesn't exist or has no files. {}", basePath.toString());
        //      allFiles = new FileStatus[0];
        //    }
        //
        //    final LinkedList<FileStatus> files = new LinkedList<FileStatus>();
        //    for (final FileStatus f : allFiles)
        //    {
        //      if (f.isDir())
        //      {
        //        final Path metadataPath = new Path(f.getPath(), "metadata");
        //        if (fileSystem.exists(metadataPath))
        //        {
        //          log.debug("Using directory: {}", f.getPath().toString());
        //          files.add(f);
        //        }
        //        else
        //        {
        //          log.warn("Skipping directory: {}", f.getPath().toString());
        //        }
        //      }
        //      else
        //      {
        //        log.debug("Skipping file: {}", f.getPath().toString());
        //      }
        //    }
        //    return files;
    }

    /*
     * GetTile implementation
     */
    private static void getTile(final HttpServletRequest request, final HttpServletResponse response,
            final Properties providerProperties) throws Exception {
        final String layer = ServletUtils.validateAndGetParamValue(request, "layer", "string");
        final int tileRow = Integer.valueOf(ServletUtils.validateAndGetParamValue(request, "tilerow", "int"));
        final int tileCol = Integer.valueOf(ServletUtils.validateAndGetParamValue(request, "tilecol", "int"));
        final double scale = Double.valueOf(ServletUtils.validateAndGetParamValue(request, "scale", "double"));
        final String style = ServletUtils.getParamValue(request, "style");

        final String imageFormat = ServletUtils.getParamValue(request, "format");

        final ImageRenderer renderer = (ImageRenderer) ImageHandlerFactory.getHandler(imageFormat,
                ImageRenderer.class);
        // TODO: Need to construct provider properties from the WebRequest using
        // a new security layer and pass those properties.
        Raster result = renderer.renderImage(layer, tileCol, tileRow, scale, (Properties) null);

        result = colorRaster(layer, style, imageFormat, renderer, result);

        ((ImageResponseWriter) ImageHandlerFactory.getHandler(imageFormat, ImageResponseWriter.class)).write(result,
                tileCol, tileRow, scale, MrsImagePyramid.open(layer, providerProperties), response);
    }

    private static Raster colorRaster(String layer, String style, String imageFormat, ImageRenderer renderer,
            Raster result) throws Exception {
        if (!(renderer instanceof TiffImageRenderer)) {
            log.debug("Applying color scale to image {} ...", layer);

            ColorScale cs;
            if (style != null) {
                cs = ColorScaleManager.fromName(style);
                if (cs == null) {
                    throw new ServletException("Can not load style: " + style);
                }
            } else {
                cs = ColorScale.createDefaultGrayScale();
            }
            result = ((ColorScaleApplier) ImageHandlerFactory.getHandler(imageFormat, ColorScaleApplier.class))
                    .applyColorScale(result, cs, renderer.getExtrema(), renderer.getDefaultValues());
            log.debug("Color scale applied to image {}", layer);
        }

        return result;
    }

    //  /**
    //   * Sets up the home data directory
    //   */
    //  @Override
    //  public void init(final ServletConfig conf) throws ServletException
    //  {
    //    super.init(conf);
    //    try
    //    {
    //      if (basePath == null)
    //      {
    //        basePath = new Path(Configuration.getInstance().getProperties().getProperty("image.base"));
    //      }
    //      System.out.println("image base path: " + basePath.toString());
    //      log.debug("image base path: {}", basePath.toString());
    //      if (colorScaleBasePath == null)
    //      {
    //        colorScaleBasePath = new Path(Configuration.getInstance().getProperties().getProperty(
    //          "colorscale.base"));
    //      }
    //      System.out.println("color scale base path: " + colorScaleBasePath.toString());
    //    }
    //    catch (final IllegalStateException e)
    //    {
    //      throw new ServletException("image.base must be specified in the MrGeo configuration file (" +
    //        e.getMessage() + ")");
    //    }
    //  }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {
        final long start = System.currentTimeMillis();
        try {
            log.debug("Semaphores available: {}", semaphore.availablePermits());
            semaphore.acquire();
            log.debug("Semaphore acquired.  Semaphores available: {}", semaphore.availablePermits());

            ServletUtils.printRequestURL(request);
            ServletUtils.printRequestAttributes(request);
            ServletUtils.printRequestParams(request);

            final String cache = ServletUtils.getParamValue(request, "cache");
            if (!StringUtils.isEmpty(cache) && cache.toLowerCase().equals("off")) {
                response.setHeader("Cache-Control", "no-store");
                response.setHeader("Pragma", "no-cache");
                response.setDateHeader("Expires", 0);
            } else {
                response.setHeader("Cache-Control", "max-age=3600");
                response.setHeader("Pragma", "");
                response.setDateHeader("Expires", 3600);
            }

            String requestParam = ServletUtils.getParamValue(request, "request");
            if (requestParam == null || requestParam.isEmpty()) {
                requestParam = "GetCapabilities";
            }
            requestParam = requestParam.toLowerCase();

            String serviceParam = ServletUtils.getParamValue(request, "service");
            if (serviceParam == null || serviceParam.isEmpty()) {
                serviceParam = "wms";
            }
            if (!serviceParam.toLowerCase().equals("wms")) {
                throw new Exception(
                        "Invalid service type was requested. (only WMS is supported '" + serviceParam + "')");
            }

            if (requestParam.equals("getmap") || requestParam.equals("getmosaic")
                    || requestParam.equals("gettile")) {
                if (!requestParam.equals("gettile")) {
                    ServletUtils.validateParam(request, "layers", "string");
                } else {
                    ServletUtils.validateParam(request, "layer", "string");
                }
                ServletUtils.validateParam(request, "format", "string");
                final String cs = ServletUtils.getParamValue(request, "crs");
                if (!StringUtils.isEmpty(cs)) {
                    if (!cs.toUpperCase().equals("CRS:84")) {
                        throw new Exception("InvalidCRS: Invalid coordinate system \"" + cs
                                + "\".  Only coordinate system CRS:84 is supported.");
                    }
                }

                OpImageRegistrar.registerMrGeoOps();
            }

            // TODO: Need to construct provider properties from the WebRequest using
            // a new security layer and pass those properties to MapAlgebraJob.
            Properties providerProperties = SecurityUtils.getProviderProperties();
            if (requestParam.equals("getcapabilities")) {
                getCapabilities(request, response, providerProperties);
            } else if (requestParam.equals("getmap")) {
                getMap(request, response, providerProperties);
            } else if (requestParam.equals("getmosaic")) {
                getMosaic(request, response, providerProperties);
            } else if (requestParam.equals("gettile")) {
                getTile(request, response, providerProperties);
            } else if (requestParam.equals("describetiles")) {
                describeTiles(request, response, providerProperties);
            } else {
                throw new Exception("Invalid request type made.");
            }
        } catch (final Exception e) {
            e.printStackTrace();
            try {
                response.setContentType("text/xml");
                writeError(e, response);
            }
            // we already started writing out to HTTP, instead return an error.
            catch (final Exception exception) {
                log.warn("Exception writing error: {}", exception);
                throw new IOException("Exception while writing XML exception (ah, the irony). "
                        + "Original Exception is below." + exception.getLocalizedMessage(), e);
            }
        } finally {
            semaphore.release();

            if (log.isDebugEnabled()) {
                log.debug("Semaphore released.  Semaphores available: {}", semaphore.availablePermits());
                log.debug("WMS request time: {}ms", (System.currentTimeMillis() - start));
                // this can be resource intensive.
                System.gc();
                final Runtime rt = Runtime.getRuntime();
                log.debug(String.format("WMS request memory: %.1fMB / %.1fMB\n",
                        (rt.totalMemory() - rt.freeMemory()) / 1e6, rt.maxMemory() / 1e6));
            }
        }
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

    /*
     * DescribeTiles implementation
     */
    private void describeTiles(final HttpServletRequest request, final HttpServletResponse response,
            final Properties providerProperties) throws IOException, ServletException, TransformerException {
        String versionStr = ServletUtils.getParamValue(request, "version");
        if (versionStr == null || versionStr.isEmpty()) {
            versionStr = "1.4.0";
        }
        version = new Version(versionStr);
        if (version.isLess("1.4.0")) {
            throw new ServletException("Describe tiles is only supported with version >= 1.4.0");
        }

        final DescribeTilesDocumentGenerator docGen = new DescribeTilesDocumentGenerator();
        final Document doc = docGen.generateDoc(version, request.getRequestURL().toString(),
                getPyramidFilesList(providerProperties));

        final PrintWriter out = response.getWriter();
        // DocumentUtils.checkForErrors(doc);
        DocumentUtils.writeDocument(doc, version, out);
    }

    /*
     * GetCapabilities implementation
     */
    private void getCapabilities(final HttpServletRequest request, final HttpServletResponse response,
            final Properties providerProperties) throws Exception {
        String versionStr = ServletUtils.getParamValue(request, "version");
        if (versionStr == null || versionStr.isEmpty()) {
            versionStr = "1.1.1";
        }
        version = new Version(versionStr);
        // conform to the version negotiation standards of WMS.
        if (version.isLess("1.3.0")) {
            versionStr = "1.1.1";
            version = new Version(versionStr);
        } else if (version.isLess("1.4.0")) {
            versionStr = "1.3.0";
            version = new Version(versionStr);
        } else {
            versionStr = "1.4.0";
            version = new Version(versionStr);
        }

        final GetCapabilitiesDocumentGenerator docGen = new GetCapabilitiesDocumentGenerator();
        final Document doc = docGen.generateDoc(version, request.getRequestURL().toString(),
                getPyramidFilesList(providerProperties));

        final PrintWriter out = response.getWriter();
        // DocumentUtils.checkForErrors(doc);
        DocumentUtils.writeDocument(doc, version, out);
    }

    /*
     * Writes OGC spec error messages to the response
     */
    private void writeError(final Exception e, final HttpServletResponse response)
            throws ParserConfigurationException, IOException, TransformerException {
        response.reset();

        Document doc;
        final DocumentBuilderFactory dBF = DocumentBuilderFactory.newInstance();
        final DocumentBuilder builder = dBF.newDocumentBuilder();
        doc = builder.newDocument();

        final Element ser = doc.createElement("ServiceExceptionReport");
        doc.appendChild(ser);
        ser.setAttribute("version", WMS_VERSION);
        final Element se = XmlUtils.createElement(ser, "ServiceException");
        String code = e.getLocalizedMessage();
        if (code == null || code.isEmpty()) {
            code = e.getClass().getName();
        }
        se.setAttribute("code", code);
        final ByteArrayOutputStream strm = new ByteArrayOutputStream();
        e.printStackTrace(new PrintStream(strm));
        se.setAttribute("locator", strm.toString());
        final PrintWriter out = response.getWriter();

        DocumentUtils.writeDocument(doc, version, out);
    }
}