/* (c) 2014-2015 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.web.data.resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.form.validation.IFormValidator;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.validation.validator.PatternValidator;
import org.geoserver.catalog.CatalogBuilder;
import org.geoserver.catalog.KeywordInfo;
import org.geoserver.catalog.ProjectionPolicy;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.wicket.CRSPanel;
import org.geoserver.web.wicket.EnvelopePanel;
import org.geoserver.web.wicket.FeedbackMessageCleaner;
import org.geoserver.web.wicket.GeoServerAjaxFormLink;
import org.geoserver.web.wicket.KeywordsEditor;
import org.geoserver.web.wicket.LiveCollectionModel;
import org.geoserver.web.wicket.SRSToCRSModel;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* A generic configuration panel for all basic ResourceInfo properties
*/
public class BasicResourceConfig extends ResourceConfigurationPanel {
private static final long serialVersionUID = -552158739086379566L;
DropDownChoice<ProjectionPolicy> projectionPolicy;
CRSPanel declaredCRS;
public BasicResourceConfig(String id, IModel<ResourceInfo> model) {
super(id, model);
TextField<String> name = new TextField<String>("name");
name.setRequired(true);
add(name);
add(new CheckBox("enabled"));
add(new CheckBox("advertised"));
add(new TextField<String>("title"));
add(new TextArea<String>("abstract"));
add(new KeywordsEditor("keywords",
LiveCollectionModel.list(new PropertyModel<List<KeywordInfo>>(model, "keywords"))));
add(new MetadataLinkEditor("metadataLinks", model));
add(new DataLinkEditor("dataLinks", model));
final Form<ResourceInfo> refForm = new Form<ResourceInfo>("referencingForm");
add(refForm);
// native bbox
PropertyModel<ReferencedEnvelope> nativeBBoxModel = new PropertyModel<ReferencedEnvelope>(model, "nativeBoundingBox");
final EnvelopePanel nativeBBox = new EnvelopePanel("nativeBoundingBox", nativeBBoxModel);
nativeBBox.setOutputMarkupId(true);
refForm.add(nativeBBox);
refForm.add(computeNativeBoundsLink(refForm, nativeBBox));
// lat/lon bbox
final EnvelopePanel latLonPanel = new EnvelopePanel("latLonBoundingBox", new PropertyModel<ReferencedEnvelope>(
model, "latLonBoundingBox"));
latLonPanel.setOutputMarkupId(true);
latLonPanel.setRequired(true);
refForm.add(latLonPanel);
refForm.add(computeLatLonBoundsLink(refForm, nativeBBox, latLonPanel));
// native srs , declared srs, and srs handling dropdown
CRSPanel nativeCRS = new CRSPanel("nativeSRS", new PropertyModel<CoordinateReferenceSystem>(model, "nativeCRS"));
nativeCRS.setReadOnly(true);
refForm.add(nativeCRS);
declaredCRS = new CRSPanel("declaredSRS",
new SRSToCRSModel(new PropertyModel<String>(model, "sRS")));
declaredCRS.setRequired(true);
refForm.add(declaredCRS);
//compute from native or declared crs links
refForm.add(computeBoundsFromSRS(refForm, nativeBBox));
projectionPolicy = new DropDownChoice<ProjectionPolicy>("srsHandling", new PropertyModel<ProjectionPolicy>(model,
"projectionPolicy"), Arrays.asList(ProjectionPolicy.values()),
new ProjectionPolicyRenderer());
ResourceInfo ri = (ResourceInfo) model.getObject();
if (((ResourceInfo) model.getObject()).getCRS() == null) {
// no native, the only meaningful policy is to force
ri.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED);
}
refForm.add(projectionPolicy);
refForm.add(new ReprojectionIsPossibleValidator(nativeCRS, declaredCRS, projectionPolicy));
}
AjaxSubmitLink computeNativeBoundsLink(final Form refForm,
final EnvelopePanel nativeBBox) {
return new AjaxSubmitLink("computeNative", refForm) {
private static final long serialVersionUID = 3106345307476297622L;
@Override
public void onSubmit(final AjaxRequestTarget target, Form form) {
// perform manual processing otherwise the component contents won't be updated
form.process(null);
ResourceInfo resource = (ResourceInfo) BasicResourceConfig.this.getDefaultModelObject();
try {
CatalogBuilder cb = new CatalogBuilder(GeoServerApplication.get().getCatalog());
ReferencedEnvelope bounds = cb.getNativeBounds(resource);
resource.setNativeBoundingBox(bounds);
nativeBBox.setModelObject(bounds);
} catch(IOException e) {
LOGGER.log(Level.SEVERE, "Error computing the native BBOX", e);
error("Error computing the native BBOX:" + e.getMessage());
}
target.add(nativeBBox);
}
public boolean getDefaultFormProcessing() {
// disable the default processing or the link won't trigger
// when any validation fails
return false;
}
};
}
/**
* Compute the native bounds from the native CRS. Acts as an alternative to computing the bounds
* from the data itself.
*/
AjaxSubmitLink computeBoundsFromSRS(final Form<ResourceInfo> refForm, final EnvelopePanel nativeBoundsPanel) {
return new AjaxSubmitLink("computeLatLonFromNativeSRS", refForm) {
private static final long serialVersionUID = 9211250161114770325L;
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
LOGGER.log(Level.FINE, "Computing bounds from native CRS");
ResourceInfo resource =
(ResourceInfo) BasicResourceConfig.this.getDefaultModelObject();
CatalogBuilder cb = new CatalogBuilder(GeoServerApplication.get().getCatalog());
ReferencedEnvelope nativeBBox = cb.getBoundsFromCRS(resource);
if (nativeBBox != null) {
nativeBoundsPanel.setModelObject(nativeBBox);
}
target.add(nativeBoundsPanel);
}
@Override
public boolean getDefaultFormProcessing() {
return false;
}
};
}
AjaxSubmitLink computeLatLonBoundsLink(final Form refForm,
final EnvelopePanel nativeBBox, final EnvelopePanel latLonPanel) {
return new AjaxSubmitLink("computeLatLon", refForm) {
private static final long serialVersionUID = -5981662004745936762L;
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
// perform manual processing of the required fields
form.process(null);
form.visitFormComponents(new FeedbackMessageCleaner<>(FeedbackMessage.UNDEFINED));
ReferencedEnvelope nativeBounds = (ReferencedEnvelope) nativeBBox.getModelObject();
try {
// if the native bounds are not around compute them
if(nativeBounds == null) {
ResourceInfo resource = (ResourceInfo) BasicResourceConfig.this.getDefaultModelObject();
CatalogBuilder cb = new CatalogBuilder(GeoServerApplication.get().getCatalog());
nativeBounds = cb.getNativeBounds(resource);
resource.setNativeBoundingBox(nativeBounds);
nativeBBox.setModelObject(nativeBounds);
target.add(nativeBBox);
}
CatalogBuilder cb = new CatalogBuilder(GeoServerApplication.get().getCatalog());
latLonPanel.setModelObject(cb.getLatLonBounds(nativeBounds, declaredCRS.getCRS()));
} catch(IOException e) {
LOGGER.log(Level.SEVERE, "Error computing the geographic BBOX", e);
error("Error computing the geographic bounds:" + e.getMessage());
}
target.add(latLonPanel);
}
@Override
public boolean getDefaultFormProcessing() {
// disable the default processing or the link won't trigger
// when any validation fails
return false;
}
};
}
class ProjectionPolicyRenderer extends ChoiceRenderer<ProjectionPolicy> {
private static final long serialVersionUID = -6593748590058977418L;
public Object getDisplayValue(ProjectionPolicy object) {
return new StringResourceModel(object.name(),
BasicResourceConfig.this, null).getString();
}
public String getIdValue(ProjectionPolicy object, int index) {
return object.name();
}
}
/**
* Checks a resource name is actually a valid one (WFS/WMS wise),
* in particular, only word chars
*/
static class ResourceNameValidator extends PatternValidator {
private static final long serialVersionUID = 2160813837236916013L;
public ResourceNameValidator() {
super("[\\w][\\w.-]*");
}
}
/**
* Form validator that checks whether the native CRS can be projected to the declared one
* whenever the projection policy chosen is "reproject"
*/
private static class ReprojectionIsPossibleValidator implements IFormValidator {
private static final long serialVersionUID = -8006718598046409480L;
private FormComponent<?>[] dependentFormComponents;
private FormComponent<?> nativeCRS;
private FormComponent<?> declaredCRS;
private FormComponent<?> projectionPolicy;
public ReprojectionIsPossibleValidator(final FormComponent<?> nativeCRS,
final FormComponent<?> declaredCRS, final FormComponent<?> projectionPolicy) {
this.nativeCRS = nativeCRS;
this.declaredCRS = declaredCRS;
this.projectionPolicy = projectionPolicy;
this.dependentFormComponents = new FormComponent[] { nativeCRS, declaredCRS,
projectionPolicy };
}
public FormComponent<?>[] getDependentFormComponents() {
return dependentFormComponents;
}
public void validate(final Form<?> form) {
CoordinateReferenceSystem nativeCrs;
CoordinateReferenceSystem declaredCrs;
ProjectionPolicy policy;
nativeCrs = (CoordinateReferenceSystem) nativeCRS.getConvertedInput();
declaredCrs = (CoordinateReferenceSystem) declaredCRS.getConvertedInput();
policy = (ProjectionPolicy) projectionPolicy.getConvertedInput();
if (policy == ProjectionPolicy.REPROJECT_TO_DECLARED) {
final boolean lenient = true;
try {
CRS.findMathTransform(nativeCrs, declaredCrs, lenient);
} catch (FactoryException e) {
String msgKey = "BasicResourceConfig.noTransformFromNativeToDeclaredCRS";
String errMsg = e.getMessage();
String message =(String) new ResourceModel(msgKey).getObject();
form.error(message, Collections.singletonMap("error", (Object) errMsg));
}
}
}
}
}