/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* 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.constellation.map.configuration;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import org.apache.sis.storage.DataStoreException;
import org.constellation.admin.StyleBusiness;
import org.constellation.business.ILayerBusiness;
import org.constellation.business.IStyleBusiness;
import org.constellation.configuration.ConfigProcessException;
import org.constellation.configuration.ConfigurationException;
import org.constellation.configuration.Instance;
import org.constellation.configuration.TargetNotFoundException;
import org.constellation.dto.AddLayer;
import org.constellation.dto.CoverageDataDescription;
import org.constellation.dto.DataDescription;
import org.constellation.dto.FeatureDataDescription;
import org.constellation.dto.PropertyDescription;
import org.constellation.ogc.configuration.OGCConfigurer;
import org.constellation.process.ConstellationProcessFactory;
import org.constellation.process.service.AddLayerToMapServiceDescriptor;
import org.constellation.provider.DataProvider;
import org.constellation.provider.DataProviders;
import org.constellation.provider.StyleProvider;
import org.constellation.provider.StyleProviders;
import org.constellation.provider.configuration.ProviderParameters;
import org.constellation.util.DataReference;
import org.constellation.ws.CstlServiceException;
import org.constellation.ws.rs.LayerProviders;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.process.ProcessDescriptor;
import org.geotoolkit.process.ProcessException;
import org.geotoolkit.process.ProcessFinder;
import org.geotoolkit.style.MutableStyle;
import org.geotoolkit.style.MutableStyleFactory;
import org.geotoolkit.style.function.InterpolationPoint;
import org.geotoolkit.style.function.Method;
import org.geotoolkit.style.function.Mode;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.style.ChannelSelection;
import org.opengis.style.ColorMap;
import org.opengis.style.ContrastEnhancement;
import org.opengis.style.ContrastMethod;
import org.opengis.style.Description;
import org.opengis.style.OverlapBehavior;
import org.opengis.style.RasterSymbolizer;
import org.opengis.style.ShadedRelief;
import org.opengis.style.Symbolizer;
import org.opengis.util.NoSuchIdentifierException;
import javax.inject.Inject;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import java.awt.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import static org.geotoolkit.style.StyleConstants.DEFAULT_CATEGORIZE_LOOKUP;
import static org.geotoolkit.style.StyleConstants.DEFAULT_DESCRIPTION;
import static org.geotoolkit.style.StyleConstants.DEFAULT_FALLBACK;
import static org.geotoolkit.style.StyleConstants.DEFAULT_GEOM;
import static org.geotoolkit.style.StyleConstants.LITERAL_ONE_FLOAT;
/**
* {@link org.constellation.configuration.ServiceConfigurer} base for "map" services.
*
* @author Fabien Bernard (Geomatys).
* @author Benjamin Garcia (Geomatys).
* @author Cédric Briançon (Geomatys).
* @version 0.9
* @since 0.9
*/
public class MapConfigurer extends OGCConfigurer {
@Inject
IStyleBusiness styleBusiness;
@Inject
ILayerBusiness layerBusiness;
@Inject
LayerProviders layerProviders;
/**
* Returns a Constellation {@link ProcessDescriptor} from its name.
*
* @param name the process descriptor name
* @return a {@link ProcessDescriptor} instance
*/
protected ProcessDescriptor getProcessDescriptor(final String name) {
try {
return ProcessFinder.getProcessDescriptor(ConstellationProcessFactory.NAME, name);
} catch (NoSuchIdentifierException ex) { // unexpected
throw new IllegalStateException("Unexpected error has occurred", ex);
}
}
/**
* Adds a new layer to a "map" service instance.
*
* @param addLayerData the layer to be added
* @throws TargetNotFoundException if the service with specified identifier does not exist
* @throws ConfigurationException if the operation has failed for any reason
*/
@Deprecated
public void addLayer(final AddLayer addLayerData) throws ConfigurationException {
serviceBusiness.ensureExistingInstance(addLayerData.getServiceType(), addLayerData.getServiceId());
final DataProvider provider = DataProviders.getInstance().getProvider(addLayerData.getProviderId());
final String namespace;
if (addLayerData.getLayerNamespace() != null) {
namespace = addLayerData.getLayerNamespace();
} else {
namespace = ProviderParameters.getNamespace(provider);
}
final String layerId = (namespace != null && !namespace.isEmpty()) ? "{" + namespace + "}" + addLayerData.getLayerId() : addLayerData.getLayerId();
// Set layer provider reference.
final DataReference layerProviderReference = DataReference.createProviderDataReference(DataReference.PROVIDER_LAYER_TYPE, addLayerData.getProviderId(), layerId);
// Set style provider reference.
DataReference styleProviderReference;
try {
final DataDescription dataDescription = layerProviders.getDataDescription(addLayerData.getProviderId(), addLayerData.getLayerId());
if (dataDescription instanceof FeatureDataDescription) {
final PropertyDescription geometryProp = ((FeatureDataDescription) dataDescription).getGeometryProperty();
if (Polygon.class.isAssignableFrom(geometryProp.getType()) || MultiPolygon.class.isAssignableFrom(geometryProp.getType())) {
styleProviderReference = DataReference.createProviderDataReference(DataReference.PROVIDER_STYLE_TYPE, "sld", "default-polygon");
} else if (LineString.class.isAssignableFrom(geometryProp.getType()) || MultiLineString.class.isAssignableFrom(geometryProp.getType()) || LinearRing.class.isAssignableFrom(geometryProp.getType())) {
styleProviderReference = DataReference.createProviderDataReference(DataReference.PROVIDER_STYLE_TYPE, "sld", "default-line");
} else {
styleProviderReference = DataReference.createProviderDataReference(DataReference.PROVIDER_STYLE_TYPE, "sld", "default-point");
}
} else {
styleProviderReference = generateDefaultStyleProviderReference((CoverageDataDescription)dataDescription, addLayerData.getLayerId());
}
} catch (IOException | DataStoreException | CstlServiceException ex) {
LOGGER.log(Level.INFO, "Error when trying to find an appropriate default style. Fallback to a raster style.");
styleProviderReference = DataReference.createProviderDataReference(DataReference.PROVIDER_STYLE_TYPE, "sld", "default-raster");
}
// Declare the style as "applicable" for the layer data.
// try {
// final QName layerID = new QName(layerProviderReference.getLayerId().getNamespaceURI(), layerProviderReference.getLayerId().getLocalPart());
// styleBusiness.linkToData(
// styleProviderReference.getProviderId(),
// styleProviderReference.getLayerId().getLocalPart(),
// layerProviderReference.getProviderId(),
// layerID);
// } catch (ConfigurationException ex) {
// LOGGER.log(Level.WARNING, "Error when associating layer default style to the layer source data.", ex);
// }
// Build descriptor.
final ProcessDescriptor desc = getProcessDescriptor("service.add_layer");
final ParameterValueGroup inputs = desc.getInputDescriptor().createValue();
inputs.parameter(AddLayerToMapServiceDescriptor.LAYER_REF_PARAM_NAME).setValue(layerProviderReference);
inputs.parameter(AddLayerToMapServiceDescriptor.LAYER_ALIAS_PARAM_NAME).setValue(addLayerData.getLayerAlias());
inputs.parameter(AddLayerToMapServiceDescriptor.LAYER_STYLE_PARAM_NAME).setValue(styleProviderReference);
inputs.parameter(AddLayerToMapServiceDescriptor.SERVICE_TYPE_PARAM_NAME).setValue(addLayerData.getServiceType());
inputs.parameter(AddLayerToMapServiceDescriptor.SERVICE_INSTANCE_PARAM_NAME).setValue(addLayerData.getServiceId());
// Call process.
try {
desc.createProcess(inputs).call();
} catch (ProcessException ex) {
throw new ConfigProcessException("Process to add a layer has reported an error.", ex);
}
}
private DataReference generateDefaultStyleProviderReference(final CoverageDataDescription covDesc, final String layerName) throws IOException, DataStoreException, CstlServiceException {
// Determine if we should apply this palette (should be applied only for geophysics data!)
// HACK: normally we should test if the view types set contains photographic, but this is not working here
// because all coverage readers seems to have it ... so just test the number of sample dimensions.
// It won't work for all cases ...
// TODO: fix netcdf reader, should not add photographic in the view types possibilities
if (covDesc.getBands().isEmpty() || covDesc.getBands().size() == 3 || covDesc.getBands().size() == 4) {
//if (coverage.getViewTypes().contains(ViewType.PHOTOGRAPHIC)) {
// should be RGB, no need to apply a palette, let the renderer display this image unchanged
return DataReference.createProviderDataReference(DataReference.PROVIDER_STYLE_TYPE, "sld", "default-raster");
}
double min = covDesc.getBands().get(0).getMinValue();
double max = covDesc.getBands().get(0).getMaxValue();
// Style.
final MutableStyleFactory SF = (MutableStyleFactory) FactoryFinder.getStyleFactory(
new Hints(Hints.STYLE_FACTORY, MutableStyleFactory.class));
double average = (max + min) / 2;
final List<InterpolationPoint> values = new ArrayList<>();
values.add(SF.interpolationPoint(Float.NaN, SF.literal(new Color(0, 0, 0, 0))));
values.add(SF.interpolationPoint(min, SF.literal(new Color(0, 54, 204, 255))));
values.add(SF.interpolationPoint(average, SF.literal(new Color(255, 254, 162, 255))));
values.add(SF.interpolationPoint(max, SF.literal(new Color(199, 8, 30, 255))));
final Expression lookup = DEFAULT_CATEGORIZE_LOOKUP;
final Literal fallback = DEFAULT_FALLBACK;
final Function function = SF.interpolateFunction(
lookup, values, Method.COLOR, Mode.LINEAR, fallback);
final ChannelSelection selection = SF.channelSelection(SF.selectedChannelType("0", (ContrastEnhancement) null));
final Expression opacity = LITERAL_ONE_FLOAT;
final OverlapBehavior overlap = OverlapBehavior.LATEST_ON_TOP;
final ColorMap colorMap = SF.colorMap(function);
final ContrastEnhancement enhance = SF.contrastEnhancement(LITERAL_ONE_FLOAT, ContrastMethod.NONE);
final ShadedRelief relief = SF.shadedRelief(LITERAL_ONE_FLOAT);
final Symbolizer outline = null;
final Unit uom = NonSI.PIXEL;
final String geom = DEFAULT_GEOM;
final String symbName = "raster symbol name";
final Description desc = DEFAULT_DESCRIPTION;
final RasterSymbolizer symbol = SF.rasterSymbolizer(
symbName, geom, desc, uom, opacity, selection, overlap, colorMap, enhance, relief, outline);
final MutableStyle style = SF.style(symbol);
style.setDefaultSpecification(symbol);
final StyleProvider provider = StyleProviders.getInstance().getProvider("sld");
// Add style into provider.
final String styleName = "default_"+ layerName;
provider.set(styleName, style);
return DataReference.createProviderDataReference(DataReference.PROVIDER_STYLE_TYPE, "sld", styleName);
}
/**
* {@inheritDoc}
*/
@Override
public Instance getInstance(String spec, String identifier) throws ConfigurationException {
final Instance instance = super.getInstance(spec, identifier);
instance.setLayersNumber(layerBusiness.getLayers(spec, identifier, null).size());
return instance;
}
}