/** * OrbisGIS is a java GIS application dedicated to research in GIScience. * OrbisGIS is developed by the GIS group of the DECIDE team of the * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>. * * The GIS group of the DECIDE team is located at : * * Laboratoire Lab-STICC – CNRS UMR 6285 * Equipe DECIDE * UNIVERSITÉ DE BRETAGNE-SUD * Institut Universitaire de Technologie de Vannes * 8, Rue Montaigne - BP 561 56017 Vannes Cedex * * OrbisGIS is distributed under GPL 3 license. * * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488) * Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285) * * This file is part of OrbisGIS. * * OrbisGIS is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * OrbisGIS is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * OrbisGIS. If not, see <http://www.gnu.org/licenses/>. * * For more information, please consult: <http://www.orbisgis.org/> * or contact directly: * info_at_ orbisgis.org */ package org.orbisgis.coremap.renderer.se; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.ValidationEvent; import javax.xml.bind.ValidationEventLocator; import javax.xml.bind.util.ValidationEventCollector; import net.opengis.se._2_0.core.ObjectFactory; import net.opengis.se._2_0.core.RuleType; import net.opengis.se._2_0.core.StyleType; import net.opengis.se._2_0.core.VersionType; //import org.slf4j.*; import org.orbisgis.coremap.layerModel.ILayer; import org.orbisgis.coremap.map.MapTransform; import org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle; import org.orbisgis.coremap.renderer.se.common.Description; import org.slf4j.*; //import org.slf4j.Logger; //import org.slf4j.LoggerFactory; /** * Usable representation of SE styles. This is the upper node of the symbology * encoding implementation. It offers validation and edition mechanisms as well * as the ability to render maps from a SE style. * @author Maxence Laurent * @author Alexis Guéganno */ public final class Style extends AbstractSymbolizerNode { public static final String PROP_VISIBLE = "visible"; private static final String DEFAULT_NAME = "Unnamed Style"; private static final Logger LOGGER = LoggerFactory.getLogger(Style.class); private String name; private ArrayList<Rule> rules; private ILayer layer; private boolean visible = true; private Description description = new Description(); protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** * Create a new {@code Style} associated to the given {@code ILayer}. If the * given boolean is tru, a default {@code Rule} will be added to the Style. * If not, the {@code Style} will be let empty. * @param layer * @param addDefaultRule */ public Style(ILayer layer, boolean addDefaultRule) { rules = new ArrayList<Rule>(); this.layer = layer; name = DEFAULT_NAME; if (addDefaultRule) { this.addRule(new Rule(layer)); } } /** * Build a new {@code Style} from the given se file and associated to the * given {@code ILayer}. * @param layer * @param seFile * @throws org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle * If the SE file can't be read or is not valid against the XML schemas. */ public Style(ILayer layer, String seFile) throws InvalidStyle { rules = new ArrayList<Rule>(); this.layer = layer; try { Unmarshaller u = org.orbisgis.coremap.map.JaxbContainer.JAXBCONTEXT.createUnmarshaller(); //Schema schema = u.getSchema(); ValidationEventCollector validationCollector = new ValidationEventCollector(); u.setEventHandler(validationCollector); JAXBElement<StyleType> fts = (JAXBElement<StyleType>) u.unmarshal( new FileInputStream(seFile)); StringBuilder errors = new StringBuilder(); for (ValidationEvent event : validationCollector.getEvents()) { String msg = event.getMessage(); ValidationEventLocator locator = event.getLocator(); int line = locator.getLineNumber(); int column = locator.getColumnNumber(); errors.append("Error at line "); errors.append(line); errors.append(" column "); errors.append(column); errors.append(" ("); errors.append(msg); errors.append(")\n"); } if (errors.length() == 0) { this.setFromJAXB(fts); } else { throw new SeExceptions.InvalidStyle(errors.toString()); } } catch (Exception ex) { throw new SeExceptions.InvalidStyle("Error while loading the style (" + seFile + "): " + ex); } } /** * Build a new {@code Style} associated to the given {@code ILayer} from the * given {@code JAXBElement<StyleType>}. * @param ftst * @param layer * @throws org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle */ public Style(JAXBElement<StyleType> ftst, ILayer layer) throws InvalidStyle { rules = new ArrayList<Rule>(); this.layer = layer; this.setFromJAXB(ftst); } /** * Build a new {@code Style} associated to the given {@code ILayer} from the * given {@code StyleType}. * @param fts * @param layer * @throws org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle */ public Style(StyleType fts, ILayer layer) throws InvalidStyle { rules = new ArrayList<Rule>(); this.layer = layer; this.setFromJAXBType(fts); } private void setFromJAXB(JAXBElement<StyleType> ftst) throws InvalidStyle { StyleType fts = ftst.getValue(); this.setFromJAXBType(fts); } private void setFromJAXBType(StyleType fts) throws InvalidStyle { if (fts.getName() != null) { this.name = fts.getName(); } else { name = DEFAULT_NAME; } if (fts.getRule() != null) { for (RuleType rt : fts.getRule()) { this.addRule(new Rule(rt, this.layer)); } } if(fts.getDescription() != null){ description = new Description(fts.getDescription()); } } /** * Gets the description associated to this style. * @return The description associated to this style. */ public Description getDescription(){ return description; } /** * This method copies all rules from given style and merge them within the current * style. Resulting style is done by stacking new rules over rules from current style. * (i.e. symbolizer level of new style > level from current one) * * This may alter the behaviour of ElseRules ! * @todo let the layer have several style ? * * @param style */ public void merge(Style style) { int offset = findBiggestLevel(); for (Rule r : style.getRules()) { this.addRule(r); for (Symbolizer s : r.getCompositeSymbolizer().getSymbolizerList()) { s.setLevel(s.getLevel() + offset); } } } private int findBiggestLevel() { int level = 0; for (Rule r : rules) { for (Symbolizer s : r.getCompositeSymbolizer().getSymbolizerList()) { level = Math.max(level, s.getLevel()); } } return level; } /** * This method remove everything in this feature type style */ public void clear() { this.rules.clear(); } /** * Export this {@code Style} to the given SE file, in XML format. * @param seFile */ public void export(String seFile) { try { JAXBContext jaxbContext = org.orbisgis.coremap.map.JaxbContainer.JAXBCONTEXT; Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(getJAXBElement(), new FileOutputStream(seFile)); } catch (FileNotFoundException ex) { LOGGER.error("Can't find the file "+seFile, ex); } catch (JAXBException ex) { LOGGER.error("Can't export your style into "+seFile+". May there be" + "some error in it ?", ex); } } /** * Gets a JAXB representation of this {@code Style}. * @return */ public JAXBElement<StyleType> getJAXBElement() { StyleType ftst = new StyleType(); if (this.name != null) { ftst.setName(this.name); } ftst.setVersion(VersionType.VALUE_1); // TODO List<RuleType> ruleTypes = ftst.getRule(); for (Rule r : rules) { ruleTypes.add(r.getJAXBType()); } if(description != null){ ftst.setDescription(description.getJAXBType()); } ObjectFactory of = new ObjectFactory(); return of.createStyle(ftst); } /** * Return all symbolizers from rules with a filter but not those from * a ElseFilter (i.e. fallback) rule * * @param mt * @param layerSymbolizers * @param overlaySymbolizers * * @param rules * @param fallbackRules * @todo take into account domain constraint */ public void getSymbolizers(MapTransform mt, List<Symbolizer> layerSymbolizers, //ArrayList<Symbolizer> overlaySymbolizers, List<Rule> rules, List<Rule> fallbackRules) { if(visible){ for (Rule r : this.rules) { // Only process visible rules with valid domain if (r.isDomainAllowed(mt)) { // Split standard rules and elseFilter rules if (!r.isFallbackRule()) { rules.add(r); } else { fallbackRules.add(r); } for (Symbolizer s : r.getCompositeSymbolizer().getSymbolizerList()) { // Extract TextSymbolizer into specific set => // Label are always drawn on top //if (s instanceof TextSymbolizer) { //overlaySymbolizers.add(s); //} else { layerSymbolizers.add(s); //} } } } } } public void resetSymbolizerLevels() { int level = 1; for (Rule r : rules) { for (Symbolizer s : r.getCompositeSymbolizer().getSymbolizerList()) { if (s instanceof TextSymbolizer) { s.setLevel(Integer.MAX_VALUE); } else { s.setLevel(level); level++; } } } } /** * Gets the {@code Layer} associated to this {@code Style}. * @return */ public ILayer getLayer() { return layer; } /** * Sets the {@code Layer} associated to this {@code Style}. * @param layer */ public void setLayer(ILayer layer) { this.layer = layer; } @Override public SymbolizerNode getParent() { return null; } @Override public void setParent(SymbolizerNode node) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void update() { } /** * Gets the name of this Style. * @return */ public String getName() { return name; } /** * Sets the name of this Style. * @param name */ public void setName(String name) { this.name = name; } /** * Gets the list of {@link Rule} contained in this Style. * @return */ public List<Rule> getRules() { return rules; } /** * Moves the ith {@link Rule} to position i-1 in the list of rules. * @param i * @return */ public boolean moveRuleUp(int i) { try { if (i > 0) { Rule r = rules.remove(i); rules.add(i - 1, r); return true; } } catch (IndexOutOfBoundsException ex) { } return false; } /** * Moves the ith {@link Rule} to position i+1 in the list of rules. * @param i * @return */ public boolean moveRuleDown(int i) { try { if (i < rules.size() - 1) { Rule r = rules.remove(i); rules.add(i + 1, r); return true; } } catch (IndexOutOfBoundsException ex) { } return false; } /** * Add a {@link Rule} to this {@code Style}. * @param r */ public void addRule(Rule r) { if (r != null) { r.setParent(this); rules.add(r); } } /** * Add a {@link Rule} to this {@code Style} at position {@code index}. * @param index * @param r */ public void addRule(int index, Rule r) { if (r != null) { r.setParent(this); rules.add(index, r); } } /** * Delete the ith {@link Rule} from this {@code Style}. * @param i * @return */ public boolean deleteRule(int i) { try { rules.remove(i); return true; } catch (IndexOutOfBoundsException ex) { return false; } } @Override public List<SymbolizerNode> getChildren() { List<SymbolizerNode> ls = new ArrayList<SymbolizerNode>(); ls.addAll(rules); return ls; } /** * * @return * True if the Rule is visible */ public boolean isVisible() { return visible; } /** * If set to true, the rule is visible. * @param visible */ public void setVisible(boolean visible) { boolean oldValue = this.visible; this.visible = visible; propertyChangeSupport.firePropertyChange(PROP_VISIBLE, oldValue, visible); } /** * Add a property-change listener for all properties. * The listener is called for all properties. * @param listener The PropertyChangeListener instance * @note Use EventHandler.create to build the PropertyChangeListener instance */ public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } /** * Add a property-change listener for a specific property. * The listener is called only when there is a change to * the specified property. * @param prop The static property name PROP_.. * @param listener The PropertyChangeListener instance * @note Use EventHandler.create to build the PropertyChangeListener instance */ public void addPropertyChangeListener(String prop,PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(prop, listener); } /** * Remove the specified listener from the list * @param listener The listener instance */ public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } /** * Remove the specified listener for a specified property from the list * @param prop The static property name PROP_.. * @param listener The listener instance */ public void removePropertyChangeListener(String prop,PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(prop,listener); } }