/*
* 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.gui;
import juzu.Action;
import juzu.Path;
import juzu.RequestScoped;
import juzu.Resource;
import juzu.Response;
import juzu.Route;
import juzu.View;
import juzu.plugin.ajax.Ajax;
import juzu.template.Template;
import org.constellation.configuration.ConfigDirectory;
import org.constellation.configuration.StyleBrief;
import org.constellation.configuration.StyleReport;
import org.constellation.dto.BandDescription;
import org.constellation.dto.CoverageDataDescription;
import org.constellation.dto.DataDescription;
import org.constellation.dto.StyleListBrief;
import org.constellation.gui.binding.ColorMap;
import org.constellation.gui.binding.Interpolate;
import org.constellation.gui.binding.InterpolationPoint;
import org.constellation.gui.binding.Style;
import org.constellation.gui.service.ConstellationService;
import org.constellation.gui.service.ProviderManager;
import org.constellation.gui.service.StyleService;
import org.constellation.gui.util.StyleBeanComparator;
import org.geotoolkit.style.interval.DefaultIntervalPalette;
import org.geotoolkit.style.interval.IntervalPalette;
import javax.inject.Inject;
import java.awt.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.constellation.gui.util.StyleUtilities.createDefaultStyle;
import static org.constellation.gui.util.StyleUtilities.readJson;
import static org.constellation.gui.util.StyleUtilities.toHex;
import static org.constellation.gui.util.StyleUtilities.writeJson;
/**
* StyledLayerDescriptor controller to manage edition edition.
*
* @author Fabien Bernard (Geomatys).
* @version 0.9
* @since 0.9
*/
@RequestScoped
public final class StyleController {
private static final String DEFAULT_PROVIDER_ID = "sld";
@Inject
private ProviderManager provider;
@Inject
private ConstellationService cstl;
@Inject
private StyleService service;
@Inject
@Path("style_dashboard.gtmpl")
Template dashboard;
@Inject
@Path("style_edition.gtmpl")
Template edition;
@Inject
@Path("style_list.gtmpl")
Template list;
@Inject
@Path("style_selected.gtmpl")
Template selected;
/**
* View for the style dashboard.
*
* @return the {@link juzu.Response} view
*/
@View
@Route("style/dashboard")
public Response dashboard(String category) throws IOException {
category = (category==null)?"all":category;
category = (category.equalsIgnoreCase("raster"))?"coverage":category;
final StyleListBrief listBean = service.getStyleList(category);
final int nbResults = listBean.getStyles().size();
// Truncate the list.
final List<StyleBrief> styles;
if (!listBean.getStyles().isEmpty()) {
final int endIndex = Math.min(listBean.getStyles().size(), 10);
styles = listBean.getStyles().subList(0, endIndex);
} else {
styles = new ArrayList<>(0);
}
final Map<String, Object> parameters = new HashMap<>(0);
parameters.put("category", category);
parameters.put("styles", styles);
parameters.put("nbResults", nbResults);
parameters.put("startIndex", 0);
parameters.put("nbPerPage", 10);
parameters.put("selected", null);
return dashboard.ok(parameters).withMimeType("text/html");
}
/**
* View for the style edition.
*
* @param layerProvider the layer provider id
* @param layerName the layer name
* @param styleProvider the style provider id
* @param styleName the style name
* @return the {@link juzu.Response} view
*/
@View
@Route("style/edition")
public Response edition(final String layerProvider,
final String layerName,
final String styleProvider,
final String styleName,
final String returnURL) {
try {
final DataDescription dataDescription;
if (layerProvider != null && layerName != null) {
// Existing data, load its description.
dataDescription = service.getLayerDataDescription(layerProvider, layerName);
} else {
// No data, free edition.
dataDescription = null;
}
final Style styleBody;
if (styleProvider != null && styleName != null) {
// Existing style, load it.
styleBody = service.getStyle(styleProvider, styleName);
} else {
// New style, create a default.
styleBody = createDefaultStyle(dataDescription);
}
// Go to view with appropriate input parameters.
final Map<String, Object> parameters = new HashMap<>(0);
parameters.put("layerProvider", layerProvider);
parameters.put("layerName", layerName);
parameters.put("styleProvider", styleProvider);
parameters.put("styleName", styleName);
parameters.put("dataDescription", dataDescription);
parameters.put("styleBody", writeJson(styleBody));
parameters.put("returnURL", returnURL);
parameters.put("portrayUrl", cstl.getUrlWithEndSlash() + "api/1/portrayal/portray");
return edition.ok(parameters).withMimeType("text/html");
} catch (IOException ex) {
return Response.error(ex);
}
}
/**
* Action for style update.
*
* @param styleProvider the style provider id
* @param styleName the style name
* @param styleJson the style json
* @return a status {@link juzu.Response}
*/
@Action
@Route("style/update")
public Response update(final String styleProvider, final String styleName, final String styleJson) {
try {
// Read edited JSON body.
final Style style = readJson(styleJson, Style.class);
// Update the style constellation side.
service.updateStyle(styleProvider, styleName, style);
// Return to dashboard.
return StyleController_.dashboard(null);
} catch (IOException ex) {
return Response.error(ex);
}
}
/**
* Action for style creation.
*
* @param styleName the style name
* @param styleJson the style json
* @return a status {@link juzu.Response}
*/
@Action
@Route("style/create")
public Response create(final String styleName, final String styleJson, final String returnURL) {
try {
// Read edited JSON body.
final Style style = readJson(styleJson, Style.class);
String stylePath = ConfigDirectory.getStyleDirectory().getPath();
provider.createProvider("sld", "sld", stylePath, "sld", null, null);
// Create the style.
service.createStyle(DEFAULT_PROVIDER_ID, styleName, style);
// Return to required Page.
return Response.redirect(returnURL);
} catch (IOException ex) {
return Response.error(ex);
}
}
@Ajax
@Resource
@Route("style/raster/classification")
public Response rasterClassification(final String providerId, final String layerName, final String bandIndex, final String nbIntervals) {
try {
final CoverageDataDescription coverageDesc = (CoverageDataDescription) service.getLayerDataDescription(providerId, layerName);
final int index = Integer.parseInt(bandIndex);
final BandDescription band = coverageDesc.getBands().get(index);
final int intervals = Integer.parseInt(nbIntervals);
final Color[] colors = new Color[]{Color.decode("#ffff00"), Color.decode("#ff0000")};
final IntervalPalette palette = new DefaultIntervalPalette(colors);
final ColorMap colorMap = new ColorMap();
final Interpolate function = new Interpolate();
final double step = (band.getMaxValue() - band.getMinValue()) / intervals;
for (int i = 0; i <= intervals; i++) {
final InterpolationPoint point = new InterpolationPoint();
point.setData(band.getMinValue() + (i * step));
if (i == 0) {
point.setColor(toHex(palette.interpolate(0)));
} else {
point.setColor(toHex(palette.interpolate(1 / ((double) intervals / (double) i))));
}
function.getPoints().add(point);
}
colorMap.setFunction(function);
return Response.content(200, writeJson(colorMap).getBytes()).withMimeType("text/html");
} catch (IOException ex) {
return Response.error(ex);
}
}
@Ajax
@Resource
@Route("style/select")
public Response selectStyle(final String name, final String providerId) throws IOException {
// Acquire the style details.
final StyleReport report = cstl.openClient().providers.getStyleReport(providerId, name);
// Go to view with appropriate parameters.
final Map<String, Object> parameters = new HashMap<>(0);
parameters.put("selected", report);
return selected.ok(parameters).withMimeType("text/html");
}
@Ajax
@Resource
@Route("style/filter")
public Response styleList(final String start, final String count, final String filter, final String orderBy, final String direction, final String dataTypes) throws IOException {
final StyleListBrief listBean = service.getStyleList(dataTypes);
// Search style by name.
if (!isBlank(filter)) {
final List<StyleBrief> toRemove = new ArrayList<>();
for (final StyleBrief bean : listBean.getStyles()) {
if (!containsIgnoreCase(bean.getName(), filter)) {
toRemove.add(bean);
}
}
listBean.getStyles().removeAll(toRemove);
}
final int nbResults = listBean.getStyles().size();
// Sort style by criteria.
if (!isBlank(orderBy) && !isBlank(direction)) {
Collections.sort(listBean.getStyles(), new StyleBeanComparator(orderBy, direction));
}
// Truncate the list.
final List<StyleBrief> styles;
final int intStart = Integer.parseInt(start);
final int intCount = Integer.parseInt(count);
if (!listBean.getStyles().isEmpty() && intStart < listBean.getStyles().size()) {
final int endIndex = Math.min(listBean.getStyles().size(), intStart + intCount);
styles = listBean.getStyles().subList(intStart, endIndex);
} else {
styles = new ArrayList<>(0);
}
final Map<String, Object> parameters = new HashMap<>(0);
parameters.put("styles", styles);
parameters.put("nbResults", nbResults);
parameters.put("startIndex", intStart);
parameters.put("nbPerPage", intCount);
return list.ok(parameters).withMimeType("text/html");
}
@Ajax
@Resource
@Route("style/linkData")
public Response linkStyleToData(final String styleProvider, final String styleName, final String dataProvider, final String dataName, final String namespace) {
try {
cstl.openClient().providers.linkStyleToData(styleProvider, styleName, dataProvider, dataName, namespace);
return Response.ok();
} catch (IOException ex) {
return Response.error(ex.getLocalizedMessage());
}
}
@Ajax
@Resource
@Route("style/unlinkData")
public Response unlinkStyleFromData(final String styleProvider, final String styleName, final String dataProvider, final String dataName, String namespace) {
try {
namespace = (namespace!=null &&namespace.equalsIgnoreCase("null"))? "":namespace;
cstl.openClient().providers.unlinkStyleFromData(styleProvider, styleName, dataProvider, dataName, namespace);
return Response.ok();
} catch (IOException ex) {
return Response.error(ex.getLocalizedMessage());
}
}
@Ajax
@Resource
@Route("style/delete")
public Response deleteStyle(final String providerId, final String styleName) {
try {
cstl.openClient().providers.deleteStyle(providerId, styleName);
return Response.ok();
} catch (IOException ex) {
return Response.error(ex.getLocalizedMessage());
}
}
}