All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mapfish.print.attribute.map.GenericMapAttribute Maven / Gradle / Ivy

package org.mapfish.print.attribute.map;

import com.google.common.base.Optional;
import com.google.common.collect.Lists;

import org.geotools.referencing.CRS;
import org.json.JSONException;
import org.json.JSONObject;
import org.mapfish.print.Constants;
import org.mapfish.print.ExceptionUtils;
import org.mapfish.print.attribute.ReflectiveAttribute;
import org.mapfish.print.config.Configuration;
import org.mapfish.print.config.ConfigurationException;
import org.mapfish.print.config.Template;
import org.mapfish.print.map.MapLayerFactoryPlugin;
import org.mapfish.print.parser.HasDefaultValue;
import org.mapfish.print.parser.MapfishParser;
import org.mapfish.print.wrapper.PArray;
import org.mapfish.print.wrapper.PObject;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.awt.Dimension;
import java.util.List;
import java.util.Map;

/**
 * Generic attributes for {@link org.mapfish.print.processor.map.CreateMapProcessor} and
 * {@link org.mapfish.print.processor.map.CreateOverviewMapProcessor}.
 * @param 
 */
public abstract class GenericMapAttribute
        extends ReflectiveAttribute.GenericMapAttributeValues> {

    private static final double[] DEFAULT_DPI_VALUES = {72, 120, 200, 254, 300, 600, 1200, 2400};
    /**
     * The json key for the suggested DPI values in the client config.
     */
    public static final String JSON_DPI_SUGGESTIONS = "dpiSuggestions";
    /**
     * The json key for the max DPI value in the client config.
     */
    public static final String JSON_MAX_DPI = "maxDPI";
    /**
     * The json key for the width of the map in the client config.
     */
    public static final String JSON_MAP_WIDTH = "width";
    /**
     * The json key for the height of the map in the client config.
     */
    public static final String JSON_MAP_HEIGHT = "height";
    static final String JSON_ZOOM_LEVEL_SUGGESTIONS = "scales";

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private MapfishParser mapfishJsonParser;

    private Double maxDpi = null;
    private double[] dpiSuggestions = null;
    private ZoomLevels zoomLevels = null;
    private Double zoomSnapTolerance = null;
    private ZoomLevelSnapStrategy zoomLevelSnapStrategy = null;
    private Boolean zoomSnapGeodetic = null;

    private Integer width = null;
    private Integer height = null;

    public final Double getMaxDpi() {
        return this.maxDpi;
    }

    public final void setMaxDpi(final Double maxDpi) {
        this.maxDpi = maxDpi;
    }

    /**
     * Get DPI suggestions.
     * @return  DPI suggestions
     */
    public final double[] getDpiSuggestions() {
        if (this.dpiSuggestions == null) {
            List list = Lists.newArrayList();
            for (double suggestion : DEFAULT_DPI_VALUES) {
                if (suggestion <= this.maxDpi) {
                    list.add(suggestion);
                }
            }
            double[] suggestions = new double[list.size()];
            for (int i = 0; i < suggestions.length; i++) {
                suggestions[i] = list.get(i);
            }
            return suggestions;
        }
        return this.dpiSuggestions;
    }

    /**
     * Suggestions for DPI values to use. Typically these are used by the client to create a UI for a user.
     * @param dpiSuggestions DPI suggestions
     */
    public final void setDpiSuggestions(final double[] dpiSuggestions) {
        this.dpiSuggestions = dpiSuggestions;
    }

    public final Integer getWidth() {
        return this.width;
    }

    /**
     * The width of the map in pixels. This value should match the width
     * of the sub-report in the JasperReport template.
     * @param width Width
     */
    public final void setWidth(final Integer width) {
        this.width = width;
    }

    public final Integer getHeight() {
        return this.height;
    }

    /**
     * The height of the map in pixels. This value should match the height
     * of the sub-report in the JasperReport template.
     * @param height Height
     */
    public final void setHeight(final Integer height) {
        this.height = height;
    }

    public final void setZoomLevels(final ZoomLevels zoomLevels) {
        this.zoomLevels = zoomLevels;
    }

    public final void setZoomSnapTolerance(final Double zoomSnapTolerance) {
        this.zoomSnapTolerance = zoomSnapTolerance;
    }

    public final void setZoomLevelSnapStrategy(final ZoomLevelSnapStrategy zoomLevelSnapStrategy) {
        this.zoomLevelSnapStrategy = zoomLevelSnapStrategy;
    }

    public final void setZoomSnapGeodetic(final Boolean zoomSnapGeodetic) {
        this.zoomSnapGeodetic = zoomSnapGeodetic;
    }

    //CSOFF: DesignForExtension
    @Override
    public void validate(final List validationErrors, final Configuration configuration) {
    //CSON: DesignForExtension
        if (this.width == null || this.width < 1) {
            validationErrors.add(new ConfigurationException("width field is not legal: " + this.width + " in " + getClass().getName()));
        }

        if (this.height == null || this.height < 1) {
            validationErrors.add(new ConfigurationException("height field is not legal: " + this.height + " in " + getClass().getName()));
        }

        if (this.getMaxDpi() == null || this.getMaxDpi() < 1) {
            validationErrors.add(
                    new ConfigurationException("maxDpi field is not legal: " + this.getMaxDpi() + " in " + getClass().getName()));
        }

        if (this.getMaxDpi() != null && this.getDpiSuggestions() != null) {
            for (double dpi : this.getDpiSuggestions()) {
                if (dpi < 1 || dpi > this.getMaxDpi()) {
                    validationErrors.add(new ConfigurationException(
                            "dpiSuggestions contains an invalid value: " + dpi + " in " + getClass().getName()));

                }
            }
        }
    }

    @Override
    protected final Optional getClientInfo() throws JSONException {
        final JSONObject jsonObject = new JSONObject();
        jsonObject.put(JSON_DPI_SUGGESTIONS, getDpiSuggestions());
        if (this.zoomLevels != null) {
            jsonObject.put(JSON_ZOOM_LEVEL_SUGGESTIONS, this.zoomLevels.getScales());
        }
        jsonObject.put(JSON_MAX_DPI, this.maxDpi);
        jsonObject.put(JSON_MAP_WIDTH, this.width);
        jsonObject.put(JSON_MAP_HEIGHT, this.height);
        return Optional.of(jsonObject);
    }

    /**
     * The value of {@link GenericMapAttribute}.
     */
    public abstract class GenericMapAttributeValues {
        private static final String TYPE = "type";

        /**
         * The default projection.
         */
        protected static final String DEFAULT_PROJECTION = "EPSG:3857";

        private final Dimension mapSize;
        private final Template template;
        private List mapLayers;

        /**
         * The projection of the map.
         */
        @HasDefaultValue
        public String projection = null;
        /**
         * The rotation of the map.
         */
        @HasDefaultValue
        public Double rotation = null;
        /**
         * Indicates if the map should adjust its scale/zoom level to be equal to one of those defined in the configuration file.
         * 

* * @see #isUseNearestScale() */ @HasDefaultValue public Boolean useNearestScale = null; /** * Indicates if the map should adjust its bounds. *

* * @see #isUseAdjustBounds() */ @HasDefaultValue public Boolean useAdjustBounds = null; /** * By default the normal axis order as specified in EPSG code will be used when parsing projections. However * the requestor can override this by explicitly declaring that longitude axis is first. */ @HasDefaultValue public Boolean longitudeFirst = null; /** * Should the vector style definitions be adapted to the target DPI resolution? (Default: true) *

* The style definitions are often optimized for a use with OpenLayers (which uses * a DPI value of 72). When these styles are used to print with a higher DPI value, * lines often look too thin, label are too small, etc. *

* If this property is set to `true`, the style definitions will be scaled to the target DPI value. */ @HasDefaultValue public Boolean dpiSensitiveStyle = true; /** * Constructor. * * @param template the template this map is part of. * @param mapSize the size of the map. */ public GenericMapAttributeValues(final Template template, final Dimension mapSize) { this.template = template; this.mapSize = mapSize; } /** * Validate the values provided by the request data and construct MapBounds and parse the layers. */ //CSOFF: DesignForExtension public void postConstruct() throws FactoryException { //CSON: DesignForExtension this.mapLayers = parseLayers(); } private List parseLayers() { List layerList = Lists.newArrayList(); for (int i = 0; i < this.getRawLayers().size(); i++) { try { PObject layer = this.getRawLayers().getObject(i); // only render if the opacity is greater than 0 if (Math.abs(layer.optDouble("opacity", 1.0)) > Constants.OPACITY_PRECISION) { parseSingleLayer(layerList, layer); } } catch (Throwable throwable) { throw ExceptionUtils.getRuntimeException(throwable); } } return layerList; } @SuppressWarnings("unchecked") private void parseSingleLayer(final List layerList, final PObject layer) throws Throwable { final Map layerParsers = GenericMapAttribute.this.applicationContext.getBeansOfType(MapLayerFactoryPlugin.class); for (MapLayerFactoryPlugin layerParser : layerParsers.values()) { final boolean layerApplies = layerParser.getTypeNames().contains(layer.getString(TYPE).toLowerCase()); if (layerApplies) { Object param = layerParser.createParameter(); GenericMapAttribute.this.mapfishJsonParser.parse(this.template.getConfiguration() .isThrowErrorOnExtraParameters(), layer, param, TYPE ); final MapLayer newLayer = layerParser.parse(this.template, param); if (layerList.isEmpty()) { layerList.add(newLayer); } else { final int lastLayerIndex = layerList.size() - 1; final MapLayer lastLayer = layerList.get(lastLayerIndex); Optional combinedLayer = lastLayer.tryAddLayer(newLayer); if (combinedLayer.isPresent()) { layerList.remove(lastLayerIndex); layerList.add(lastLayerIndex, combinedLayer.get()); } else { layerList.add(newLayer); } } return; } } StringBuilder message = new StringBuilder("\nLayer with type: '" + layer.getString(TYPE) + "' is not currently " + "supported. Options include: "); for (MapLayerFactoryPlugin mapLayerFactoryPlugin : layerParsers.values()) { for (Object name : mapLayerFactoryPlugin.getTypeNames()) { message.append("\n"); message.append("\t* ").append(name); } } throw new IllegalArgumentException(message.toString()); } /** * Parse the projection from a string. * @return the crs */ protected final CoordinateReferenceSystem parseProjection() { return GenericMapAttribute.parseProjection(getProjection(), this.longitudeFirst); } /** * Return the DPI value for the map. * This method is abstract because the dpi value is optional for the overview map, * but must be given for the normal map. So, in the overview map the field is defined * with a @HasDefaultValue annotation. */ public abstract Double getDpi(); /** * Return the JSON layer definiton. * This method is abstract is abstract for the same reasons as {@link #getDpi()}. */ protected abstract PArray getRawLayers(); //CSOFF: DesignForExtension public List getLayers() { //CSON: DesignForExtension return Lists.newArrayList(this.mapLayers); } public final Template getTemplate() { return this.template; } public final Dimension getMapSize() { return this.mapSize; } //CSOFF: DesignForExtension public Double getRotation() { //CSON: DesignForExtension return this.rotation; } //CSOFF: DesignForExtension public String getProjection() { //CSON: DesignForExtension return this.projection; } /** * Return true if requestData has useNearestScale and configuration has some zoom levels defined. */ //CSOFF: DesignForExtension public Boolean isUseNearestScale() { //CSON: DesignForExtension return this.useNearestScale && GenericMapAttribute.this.zoomLevels != null; } /** * Return true if requestData has useNearestScale and configuration has some zoom levels defined. */ //CSOFF: DesignForExtension public Boolean isUseAdjustBounds() { //CSON: DesignForExtension return this.useAdjustBounds; } public final Boolean isDpiSensitiveStyle() { return this.dpiSensitiveStyle; } //CSOFF: DesignForExtension public ZoomLevels getZoomLevels() { //CSON: DesignForExtension return GenericMapAttribute.this.zoomLevels; } //CSOFF: DesignForExtension public Double getZoomSnapTolerance() { //CSON: DesignForExtension return GenericMapAttribute.this.zoomSnapTolerance; } //CSOFF: DesignForExtension public ZoomLevelSnapStrategy getZoomLevelSnapStrategy() { //CSON: DesignForExtension return GenericMapAttribute.this.zoomLevelSnapStrategy; } //CSOFF: DesignForExtension public Boolean getZoomSnapGeodetic() { //CSON: DesignForExtension return GenericMapAttribute.this.zoomSnapGeodetic; } //CSOFF: DesignForExtension public double[] getDpiSuggestions() { //CSON: DesignForExtension return GenericMapAttribute.this.getDpiSuggestions(); } //CSOFF: DesignForExtension public double getRequestorDPI() { //CSON: DesignForExtension // We are making the same assumption as Openlayers 2.x versions, that the DPI is 72. // In the future we probably need to change this assumption and allow the client software to // specify the DPI they are using for creating the bounds. // For the moment we require the client to convert their bounds to 72 DPI return Constants.PDF_DPI; } /** * @param value The value or null. * @param defaultValue The default value. * @param A type. */ protected final T getValueOr(final T value, final T defaultValue) { if (value != null) { return value; } else { return defaultValue; } } } /** * Parse the given projection. * * @param projection The projection string. * @param longitudeFirst longitudeFirst */ public static CoordinateReferenceSystem parseProjection(final String projection, final Boolean longitudeFirst) { String epsgCode = projection; if (epsgCode.equalsIgnoreCase("EPSG:900913")) { epsgCode = "EPSG:3857"; } try { if (longitudeFirst == null) { return CRS.decode(epsgCode); } else { return CRS.decode(epsgCode, longitudeFirst); } } catch (NoSuchAuthorityCodeException e) { throw new RuntimeException(epsgCode + " was not recognized as a crs code", e); } catch (FactoryException e) { throw new RuntimeException("Error occurred while parsing: " + epsgCode, e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy