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

org.mapfish.print.map.style.json.MapfishJsonStyleVersion2 Maven / Gradle / Ivy

There is a newer version: 3.22.0
Show newest version
package org.mapfish.print.map.style.json;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.Symbolizer;
import org.json.JSONException;
import org.json.JSONObject;
import org.mapfish.print.ExceptionUtils;
import org.mapfish.print.config.Configuration;
import org.mapfish.print.wrapper.json.PJsonArray;
import org.mapfish.print.wrapper.json.PJsonObject;
import org.opengis.filter.Filter;
import org.springframework.http.client.ClientHttpRequestFactory;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Support a more flexible json styling than that which is supported by version 1.
 */
public final class MapfishJsonStyleVersion2 {
    static final String JSON_SYMB = "symbolizers";
    private static final String JSON_TYPE = "type";
    private static final Pattern VALUE_EXPR_PATTERN = Pattern.compile("\\$\\{([\\w\\d_-]+)\\}");
    private static final String JSON_MIN_SCALE = "minScale";
    private static final String JSON_MAX_SCALE = "maxScale";
    private static final String JSON_FILTER_INCLUDE = "*";
    private final PJsonObject json;
    private final StyleBuilder styleBuilder;
    private final JsonStyleParserHelper parserHelper;

    MapfishJsonStyleVersion2(
            @Nonnull final PJsonObject json,
            @Nonnull final StyleBuilder styleBuilder,
            @Nullable final Configuration configuration,
            @Nonnull final ClientHttpRequestFactory requestFactory) {
        this.json = json;
        this.styleBuilder = styleBuilder;
        this.parserHelper = new JsonStyleParserHelper(configuration, requestFactory, styleBuilder,
                                                      false);
    }

    @VisibleForTesting
    static Map resolveAllValues(final Map values) {
        Map toResolve = Maps.newHashMap(values);
        Map resolved = Maps.newHashMapWithExpectedSize(values.size());
        while (!toResolve.isEmpty()) {
            Iterator> entries = toResolve.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry next = entries.next();
                String value = next.getValue();
                final String resolve = resolve(values, value);
                if (resolve == null) {
                    resolved.put(next.getKey(), value);
                    entries.remove();
                } else {
                    next.setValue(resolve);
                }

            }
        }

        return resolved;
    }

    private static String resolve(final Map values, final String value) {
        int lastEnd = 0;
        final Matcher matcher = VALUE_EXPR_PATTERN.matcher(value);
        boolean changed = false;
        StringBuilder updatedValue = new StringBuilder();
        while (matcher.find()) {
            final String valName = matcher.group(1);
            String replacement;
            if (values.containsKey(valName)) {
                changed = true;
                replacement = values.get(valName);
            } else {
                replacement = matcher.group(0);
            }
            updatedValue.append(value, lastEnd, matcher.start());
            updatedValue.append(replacement);
            lastEnd = matcher.end();
        }

        if (changed) {
            updatedValue.append(value.substring(lastEnd));
            return updatedValue.toString();
        } else {
            return null;
        }
    }

    Style parseStyle() {
        List rules = Lists.newArrayList();

        final Iterator keys = this.json.keys();
        while (keys.hasNext()) {
            final String next = keys.next().trim();
            if (isRule(next)) {
                rules.add(createRule(next));
            }
        }

        if (rules.isEmpty()) {
            throw new IllegalArgumentException(String.format(
                    "No rules found in style.  Rules are json objects that have the key %s or have the " +
                            "form: [ecql]", JSON_FILTER_INCLUDE));
        }

        return this.parserHelper.createStyle(rules);
    }

    private Rule createRule(final String jsonKey) {
        final PJsonObject ruleJson = this.json.getJSONObject(jsonKey);
        Filter filter = Filter.INCLUDE;
        if (!jsonKey.equals(JSON_FILTER_INCLUDE)) {
            try {
                filter = ECQL.toFilter(jsonKey, this.styleBuilder.getFilterFactory());
            } catch (CQLException e) {
                throw new RuntimeException("Error compiling rule filter: " + jsonKey, e);
            }
        }

        final PJsonArray symbolizerJsonArray = ruleJson.getJSONArray(JSON_SYMB);
        final Symbolizer[] symbolizers = new Symbolizer[symbolizerJsonArray.size()];

        for (int i = 0; i < symbolizerJsonArray.size(); i++) {
            final PJsonObject symbolizerJson = symbolizerJsonArray.getJSONObject(i);
            updateSymbolizerProperties(ruleJson, symbolizerJson);

            SymbolizerType type = SymbolizerType.valueOf(symbolizerJson.getString(JSON_TYPE).toUpperCase());
            symbolizers[i] = type.parseJson(this.parserHelper, symbolizerJson);
            if (symbolizers[i] == null) {
                throw new RuntimeException(
                        "Error creating symbolizer " + symbolizerJson.getString(JSON_TYPE) + " in rule " +
                                jsonKey);
            }
        }

        Map ruleValues = buildValuesMap(ruleJson, new PJsonObject(new JSONObject(), "empty"));
        double minScale = getScaleDenominator(ruleValues, JSON_MIN_SCALE, Double.MIN_VALUE);
        double maxScale = getScaleDenominator(ruleValues, JSON_MAX_SCALE, Double.MAX_VALUE);
        final Rule rule = this.styleBuilder.createRule(symbolizers, minScale, maxScale);
        rule.setFilter(filter);
        return rule;
    }

    private void updateSymbolizerProperties(final PJsonObject ruleJson, final PJsonObject symbolizerJson) {
        Map values = buildValuesMap(ruleJson, symbolizerJson);
        for (Map.Entry entry: values.entrySet()) {
            try {
                symbolizerJson.getInternalObj().put(entry.getKey(), entry.getValue());
            } catch (JSONException e) {
                throw ExceptionUtils.getRuntimeException(e);
            }
        }
    }

    private double getScaleDenominator(
            final Map ruleValues, final String keyName, final double defaultValue) {
        String scaleString = ruleValues.get(keyName);
        if (scaleString != null) {
            return Double.parseDouble(scaleString);
        }
        return defaultValue;
    }

    private Map buildValuesMap(
            final PJsonObject ruleJson,
            final PJsonObject symbolizerJson) {
        Map values = Maps.newHashMap();

        Iterator keys = this.json.keys();
        while (keys.hasNext()) {
            String key = keys.next().trim();
            if (!isRule(key)) {
                values.put(key, this.json.getString(key));
            }
        }
        keys = ruleJson.keys();
        while (keys.hasNext()) {
            String key = keys.next().trim();
            if (!key.equals(JSON_SYMB)) {
                values.put(key, ruleJson.getString(key));
            }
        }

        keys = symbolizerJson.keys();
        while (keys.hasNext()) {
            String key = keys.next().trim();
            if (!key.equals(JSON_SYMB)) {
                values.put(key, symbolizerJson.getString(key));
            }
        }

        return resolveAllValues(values);
    }

    private boolean isRule(final String jsonKey) {
        return this.json.optJSONObject(jsonKey) != null;
    }

    enum SymbolizerType {
        POINT {
            @Override
            protected Symbolizer parseJson(
                    final JsonStyleParserHelper parser,
                    final PJsonObject symbolizerJson) {
                return parser.createPointSymbolizer(symbolizerJson);
            }
        }, LINE {
            @Override
            protected Symbolizer parseJson(
                    final JsonStyleParserHelper parser,
                    final PJsonObject symbolizerJson) {
                return parser.createLineSymbolizer(symbolizerJson);
            }
        }, POLYGON {
            @Override
            protected Symbolizer parseJson(
                    final JsonStyleParserHelper parser,
                    final PJsonObject symbolizerJson) {
                return parser.createPolygonSymbolizer(symbolizerJson);
            }
        }, TEXT {
            @Override
            protected Symbolizer parseJson(
                    final JsonStyleParserHelper parser,
                    final PJsonObject symbolizerJson) {
                return parser.createTextSymbolizer(symbolizerJson);
            }
        };

        protected abstract Symbolizer parseJson(
                JsonStyleParserHelper parser,
                PJsonObject symbolizerJson);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy