/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.ui.style.service.internal;
import java.awt.Color;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import javax.annotation.Nullable;
import javax.xml.namespace.QName;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.graphics.RGB;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.SLDParser;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.Symbolizer;
import org.opengis.feature.type.Name;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
import eu.esdihumboldt.hale.common.instance.model.DataSet;
import eu.esdihumboldt.hale.common.schema.SchemaSpaceID;
import eu.esdihumboldt.hale.common.schema.model.Schema;
import eu.esdihumboldt.hale.common.schema.model.SchemaSpace;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeIndex;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.AbstractFlag;
import eu.esdihumboldt.hale.ui.common.service.style.StyleService;
import eu.esdihumboldt.hale.ui.common.service.style.StyleServiceListener;
import eu.esdihumboldt.hale.ui.service.project.ProjectService;
import eu.esdihumboldt.hale.ui.service.project.ProjectServiceAdapter;
import eu.esdihumboldt.hale.ui.service.schema.SchemaService;
import eu.esdihumboldt.hale.ui.service.schema.SchemaServiceListener;
import eu.esdihumboldt.hale.ui.style.StyleHelper;
import eu.esdihumboldt.hale.ui.style.internal.InstanceStylePlugin;
/**
* A default {@link StyleService} implementation that will provide simple styles
* for Lines, Points and Polygons if none have been loaded from an SLD.
*
* @author Thorsten Reitz, Simon Templer
* @partner 01 / Fraunhofer Institute for Computer Graphics Research
*/
public class StyleServiceImpl extends AbstractStyleService {
private static final ALogger _log = ALoggerFactory.getLogger(StyleServiceImpl.class);
private final Map<TypeDefinition, FeatureTypeStyle> styles;
private final SchemaService schemaService;
/**
* Queued styles
*/
private final Queue<FeatureTypeStyle> queuedStyles = new LinkedList<FeatureTypeStyle>();
private static final StyleBuilder styleBuilder = new StyleBuilder();
private static final StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null);
private RGB background = null;
private FeatureTypeStyle fbStyle = null;
// Constructor, instance accessor ..........................................
/**
* Create a style service.
*
* @param projectService the project service
* @param schemaService the schema service
*/
public StyleServiceImpl(final ProjectService projectService, SchemaService schemaService) {
styles = new HashMap<TypeDefinition, FeatureTypeStyle>();
this.schemaService = schemaService;
// add listener to process queued styles
schemaService.addSchemaServiceListener(new SchemaServiceListener() {
@Override
public void schemaAdded(SchemaSpaceID spaceID, Schema schema) {
update();
}
@Override
public void schemasCleared(SchemaSpaceID spaceID) {
update();
}
@Override
public void mappableTypesChanged(SchemaSpaceID spaceID,
Collection<? extends TypeDefinition> types) {
update();
}
private void update() {
Collection<FeatureTypeStyle> failures = new ArrayList<FeatureTypeStyle>();
boolean updateNeeded = false;
while (!queuedStyles.isEmpty()) {
FeatureTypeStyle fts = queuedStyles.poll();
Collection<TypeDefinition> types = findTypes(fts.featureTypeNames());
if (types != null && !types.isEmpty()) {
for (TypeDefinition type : types) {
if (addStyle(type, fts)) {
updateNeeded = true;
}
}
}
else {
failures.add(fts);
}
}
queuedStyles.addAll(failures);
if (updateNeeded) {
notifyStylesAdded();
}
}
});
// listen to style preference changes
IPreferenceStore prefStore = InstanceStylePlugin.getDefault().getPreferenceStore();
prefStore.addPropertyChangeListener(new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
if (StylePreferences.ALL_KEYS.contains(property)) {
// settings changed
notifySettingsChanged();
}
}
});
// clear styles on project clean
projectService.addListener(new ProjectServiceAdapter() {
@Override
public void onClean() {
clearStyles();
}
});
// notify project service of style changes
// XXX not sure how this is done elsewhere, e.g. on alignment changes
// TODO revise?
addListener(new StyleServiceListener() {
@Override
public void stylesRemoved(StyleService styleService) {
projectService.setChanged();
}
@Override
public void stylesAdded(StyleService styleService) {
projectService.setChanged();
}
@Override
public void styleSettingsChanged(StyleService styleService) {
// this is an application wide setting
}
@Override
public void backgroundChanged(StyleService styleService, RGB background) {
// this is an application wide setting
}
});
}
// StyleService methods ....................................................
/**
* @see StyleService#getNamedStyle(String)
*/
@Override
public Style getNamedStyle(String name) {
Style style = styleFactory.createStyle();
for (FeatureTypeStyle fts : this.styles.values()) {
// XXX checks for the FeatureTypeStyle name instead of the UserStyle
// name
if (fts.getName().equals(name)) {
style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
break;
}
}
return style;
}
/**
* This implementation will build a simple style if none is defined
* previously.
*
* @see StyleService#getStyle(TypeDefinition, DataSet)
*/
@Override
public Style getStyle(TypeDefinition type, @Nullable DataSet dataSet) {
FeatureTypeStyle fts = styles.get(type);
Style style = styleFactory.createStyle();
if (fts != null) {
style.featureTypeStyles().add(fts);
}
else {
if (fbStyle != null) {
style.featureTypeStyles().add(fbStyle);
}
else {
style.featureTypeStyles().add(StyleHelper.getDefaultStyle(type, dataSet));
}
}
return style;
}
/**
* @see StyleService#getDefinedStyle(TypeDefinition)
*/
@Override
public Style getDefinedStyle(TypeDefinition type) {
FeatureTypeStyle fts = styles.get(type);
if (fts != null) {
Style style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
else {
return null;
}
}
/**
* @see StyleService#getStyle()
*/
@Override
public Style getStyle() {
Style style = styleFactory.createStyle();
for (FeatureTypeStyle fts : styles.values()) {
style.featureTypeStyles().add(fts);
}
if (fbStyle != null) {
style.featureTypeStyles().add(fbStyle);
}
return style;
}
/**
* @see StyleService#getStyle(DataSet)
*/
@Override
public Style getStyle(final DataSet dataset) {
return getStyle(dataset, false);
}
/**
* @see StyleService#getSelectionStyle(DataSet)
*/
@Override
public Style getSelectionStyle(DataSet type) {
return getStyle(type, true);
}
private Style getStyle(final DataSet dataset, boolean selected) {
SchemaSpace schemas = schemaService.getSchemas(
(dataset == DataSet.SOURCE) ? (SchemaSpaceID.SOURCE) : (SchemaSpaceID.TARGET));
Style style = styleFactory.createStyle();
for (TypeDefinition type : schemas.getMappingRelevantTypes()) {
if (!type.getConstraint(AbstractFlag.class).isEnabled()) {
// only add styles for non-abstract feature types
FeatureTypeStyle fts = styles.get(type);
if (fts == null) {
if (fbStyle != null) {
fts = fbStyle;
}
else {
fts = StyleHelper.getDefaultStyle(type, dataset);
}
}
if (selected) {
fts = getSelectedStyle(fts);
}
style.featureTypeStyles().add(fts);
}
}
return style;
}
/**
* Convert the given style for selection
*
* @param fts the feature type style to convert
*
* @return the converted feature type style
*/
@SuppressWarnings("deprecation")
private FeatureTypeStyle getSelectedStyle(FeatureTypeStyle fts) {
List<Rule> rules = fts.rules();
List<Rule> newRules = new ArrayList<Rule>();
for (Rule rule : rules) {
Symbolizer[] symbolizers = rule.getSymbolizers();
List<Symbolizer> newSymbolizers = new ArrayList<Symbolizer>();
for (Symbolizer symbolizer : symbolizers) {
// get symbolizers
List<Symbolizer> addSymbolizers = getSelectionSymbolizers(symbolizer);
if (addSymbolizers != null) {
newSymbolizers.addAll(addSymbolizers);
}
}
// create new rule
Rule newRule = styleBuilder
.createRule(newSymbolizers.toArray(new Symbolizer[newSymbolizers.size()]));
newRule.setFilter(rule.getFilter());
newRule.setIsElseFilter(rule.isElseFilter());
newRule.setName(rule.getName());
newRules.add(newRule);
}
// FIXME use featureTypeNames list
return styleBuilder.createFeatureTypeStyle(fts.getFeatureTypeName(),
newRules.toArray(new Rule[newRules.size()]));
}
/**
* Get the symbolizers representing the given symbolizer for a selection
*
* @param symbolizer the symbolizer
*
* @return the selection symbolizers
*/
private List<Symbolizer> getSelectionSymbolizers(Symbolizer symbolizer) {
List<Symbolizer> result = new ArrayList<Symbolizer>();
Color color = StylePreferences.getSelectionColor();
int width = StylePreferences.getSelectionWidth();
if (symbolizer instanceof PolygonSymbolizer) {
result.add(StyleHelper.createPolygonSymbolizer(color, width));
}
else if (symbolizer instanceof LineSymbolizer) {
result.add(StyleHelper.createLineSymbolizer(color, width));
}
else if (symbolizer instanceof PointSymbolizer) {
result.add(
StyleHelper.mutatePointSymbolizer((PointSymbolizer) symbolizer, color, width));
// result.add(createPointSymbolizer(color, width));
}
else {
// do not fall-back to original symbolizer cause we are painting
// over it
// result.add(symbolizer);
}
return result;
}
/**
* @see StyleService#addStyles(Style[])
*/
@Override
public void addStyles(Style... styles) {
boolean somethingHappened = false;
for (Style style : styles) {
for (FeatureTypeStyle fts : style.featureTypeStyles()) {
if (!fts.featureTypeNames().isEmpty() && fts.featureTypeNames().iterator().next()
.getLocalPart().equals("Feature")) {
this.fbStyle = fts;
somethingHappened = true;
}
else {
Collection<TypeDefinition> types = findTypes(fts.featureTypeNames());
if (types != null && !types.isEmpty()) {
for (TypeDefinition type : types) {
if (addStyle(type, fts)) {
somethingHappened = true;
}
}
}
else {
/*
* store for later schema update when feature type might
* be present
*/
queuedStyles.add(fts);
}
}
}
}
if (somethingHappened) {
notifyStylesAdded();
}
}
/**
* Search the available types for matching names.
*
* @param featureTypeNames the feature type names
* @return the types or <code>null</code>
*/
private Collection<TypeDefinition> findTypes(Set<Name> featureTypeNames) {
if (featureTypeNames == null) {
return null;
}
// prepare names
Set<QName> qnames = new HashSet<QName>();
Set<String> localnames = new HashSet<String>();
for (Name name : featureTypeNames) {
String ns = name.getNamespaceURI();
if (ns == null || ns.isEmpty()) {
localnames.add(name.getLocalPart());
}
else {
qnames.add(new QName(ns, name.getLocalPart()));
}
}
Collection<TypeDefinition> result = new ArrayList<TypeDefinition>();
// search source...
result.addAll(
findTypes(schemaService.getSchemas(SchemaSpaceID.SOURCE), qnames, localnames));
// and target types
result.addAll(
findTypes(schemaService.getSchemas(SchemaSpaceID.TARGET), qnames, localnames));
return result;
}
/**
* Search the given type index for matching names.
*
* @param typeIndex the type index
* @param qnames the qualified names
* @param localnames the local names
* @return the types or an empty collection
*/
private Collection<TypeDefinition> findTypes(TypeIndex typeIndex, Set<QName> qnames,
Set<String> localnames) {
Collection<TypeDefinition> result = new ArrayList<TypeDefinition>();
// check all mappable types
for (TypeDefinition type : typeIndex.getMappingRelevantTypes()) {
String name = StyleHelper.getFeatureTypeName(type);
if (localnames.contains(name)) {
result.add(type);
}
// TODO support for qnames (another method in StyleHelper?)
}
return result;
}
/*
* private Collection<TypeDefinition> findFallbackTypes(){
* Collection<TypeDefinition> result = new ArrayList<TypeDefinition>();
*
* TypeIndex typeIndexSource =
* schemaService.getSchemas(SchemaSpaceID.SOURCE); TypeIndex typeIndexTarget
* = schemaService.getSchemas(SchemaSpaceID.TARGET);
*
* for (TypeDefinition type : typeIndexSource.getMappingRelevantTypes()){
* result.add(type); } for (TypeDefinition type :
* typeIndexTarget.getMappingRelevantTypes()){ result.add(type); } return
* result; }
*/
/**
* Add a type style.
*
* @param type the type definition
* @param fts the type style
* @return if the style definitions were changed
*/
private boolean addStyle(TypeDefinition type, FeatureTypeStyle fts) {
boolean somethingHappened = false;
FeatureTypeStyle old = this.styles.get(type);
if (old != null) {
if (!old.equals(fts)) {
_log.info("Replacing style for feature type " + type.getName()); //$NON-NLS-1$
somethingHappened = true;
}
}
else {
_log.info("Adding style for feature type " + type.getName()); //$NON-NLS-1$
somethingHappened = true;
}
this.styles.put(type, fts);
return somethingHappened;
}
/**
* @see StyleService#addStyles(URL)
*/
@Override
public boolean addStyles(URL url) {
SLDParser stylereader;
try {
stylereader = new SLDParser(styleFactory, url);
Style[] styles = stylereader.readXML();
addStyles(styles);
return true;
} catch (Exception e) {
_log.error("Error reading styled layer descriptor", e); //$NON-NLS-1$
return false;
}
}
/**
* @see StyleService#getBackground()
*/
@Override
public RGB getBackground() {
if (background == null) {
return StylePreferences.getDefaultBackground();
}
else {
return background;
}
}
/**
* @see StyleService#setBackground(RGB)
*/
@Override
public void setBackground(RGB color) {
this.background = color;
notifyBackgroundChanged(color);
}
/**
* @see StyleService#clearStyles()
*/
@Override
public void clearStyles() {
queuedStyles.clear();
styles.clear();
fbStyle = null;
notifyStylesRemoved();
}
}