/* (c) 2014 - 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.gwc.web.gridset; import static com.google.common.base.Preconditions.checkNotNull; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.FormComponentPanel; import org.apache.wicket.markup.html.form.Radio; import org.apache.wicket.markup.html.form.RadioGroup; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.validation.IValidatable; import org.apache.wicket.validation.IValidator; import org.apache.wicket.validation.ValidationError; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.gwc.web.GWCIconFactory; import org.geoserver.web.wicket.DecimalTextField; import org.geoserver.web.wicket.ImageAjaxLink; import org.geoserver.wms.WMSInfo; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geowebcache.grid.BoundingBox; import org.geowebcache.grid.Grid; import org.geowebcache.grid.GridSet; import org.geowebcache.grid.GridSetFactory; import org.geowebcache.grid.SRS; public class TileMatrixSetEditor extends FormComponentPanel<List<Grid>> { private static final long serialVersionUID = 5098470663723800345L; private ListView<Grid> grids; // private Label noMetadata; private WebMarkupContainer table; private boolean readOnly; private IModel<GridSetInfo> info; private static class TileMatrixSetValidator implements IValidator<List<Grid>> { private static final long serialVersionUID = 1L; @Override public void validate(IValidatable<List<Grid>> validatable) { ValidationError error = new ValidationError(); List<Grid> grids = validatable.getValue(); if (grids == null || grids.size() == 0) { error.setMessage(new ResourceModel("TileMatrixSetEditor.validation.empty") .getObject()); validatable.error(error); return; } for (int i = 1; i < grids.size(); i++) { Grid prev = grids.get(i - 1); Grid curr = grids.get(i); if (curr.getResolution() >= prev.getResolution()) { String message = "Each resolution should be lower than it's prior one. Res[" + i + "] == " + curr.getResolution() + ", Res[" + (i - 1) + "] == " + prev.getResolution() + "."; error.setMessage(message); validatable.error(error); return; } if (curr.getScaleDenominator() >= prev.getScaleDenominator()) { String message = "Each scale denominator should be lower " + "than it's prior one. Scale[" + i + "] == " + curr.getScaleDenominator() + ", Scale[" + (i - 1) + "] == " + prev.getScaleDenominator() + "."; error.setMessage(message); validatable.error(error); return; } } } } /** * @param id * @param model * the model over the appropriate list of {@link Grid} * @see WMSInfo#getAuthorityURLs() * @see LayerInfo#getAuthorityURLs() * @see LayerGroupInfo#getAuthorityURLs() */ public TileMatrixSetEditor(final String id, final IModel<GridSetInfo> info) { super(id, new PropertyModel<List<Grid>>(info, "levels")); add(new TileMatrixSetValidator()); final IModel<List<Grid>> list = getModel(); checkNotNull(list.getObject()); this.info = info; this.readOnly = info.getObject().isInternal(); // container for ajax updates final WebMarkupContainer container = new WebMarkupContainer("container"); container.setOutputMarkupId(true); add(container); final IModel<Boolean> preserveesolutionsModel = new PropertyModel<Boolean>(info, "resolutionsPreserved"); final RadioGroup<Boolean> resolutionsOrScales = new RadioGroup<Boolean>( "useResolutionsOrScalesGroup", preserveesolutionsModel); container.add(resolutionsOrScales); Radio<Boolean> preserveResolutions = new Radio<Boolean>("preserveResolutions", new Model<Boolean>(Boolean.TRUE)); Radio<Boolean> preserveScales = new Radio<Boolean>("preserveScales", new Model<Boolean>( Boolean.FALSE)); resolutionsOrScales.add(preserveResolutions); resolutionsOrScales.add(preserveScales); // update the table when this option changes so either the resolutions or scales column is // enabled resolutionsOrScales.add(new AjaxFormChoiceComponentUpdatingBehavior() { private static final long serialVersionUID = 1L; @Override protected void onUpdate(AjaxRequestTarget target) { resolutionsOrScales.processInput(); final boolean useResolutions = resolutionsOrScales.getModelObject().booleanValue(); Iterator<Component> iterator = grids.iterator(); while (iterator.hasNext()) { @SuppressWarnings("unchecked") ListItem<Grid> next = (ListItem<Grid>) iterator.next(); next.get("resolution").setEnabled(useResolutions); next.get("scale").setEnabled(!useResolutions); } target.add(table); } }); // the link list table = new WebMarkupContainer("table"); table.setOutputMarkupId(true); table.add(thLabel("level")); table.add(thLabel("resolution")); table.add(thLabel("scale")); table.add(thLabel("name")); table.add(thLabel("tiles")); container.add(table); grids = new ListView<Grid>("gridLevels", new ArrayList<Grid>(list.getObject())) { private static final long serialVersionUID = 1L; @Override protected void onBeforeRender() { super.onBeforeRender(); } @Override protected void populateItem(final ListItem<Grid> item) { // odd/even style final int index = item.getIndex(); item.add(AttributeModifier.replace("class", index % 2 == 0 ? "even" : "odd")); item.add(new Label("zoomLevel", String.valueOf(index))); final TextField<Double> resolution; final TextField<Double> scale; final TextField<String> name; final Label tiles; final Component removeLink; resolution = new DecimalTextField("resolution", new PropertyModel<Double>( item.getModel(), "resolution")); resolution.setOutputMarkupId(true); item.add(resolution); scale = new DecimalTextField("scale", new PropertyModel<Double>(item.getModel(), "scaleDenom")); scale.setOutputMarkupId(true); item.add(scale); name = new TextField<String>("name", new PropertyModel<String>(item.getModel(), "name")); item.add(name); IModel<String> tilesModel = new IModel<String>() { private static final long serialVersionUID = 1L; @Override public String getObject() { // resolution.processInput(); Double res = resolution.getModelObject(); GridSetInfo gridSetInfo = TileMatrixSetEditor.this.info.getObject(); final ReferencedEnvelope extent = gridSetInfo.getBounds(); if (res == null || extent == null) { return "--"; } final int tileWidth = gridSetInfo.getTileWidth(); final int tileHeight = gridSetInfo.getTileHeight(); final double mapUnitWidth = tileWidth * res.doubleValue(); final double mapUnitHeight = tileHeight * res.doubleValue(); final long tilesWide = (long) Math .ceil((extent.getWidth() - mapUnitWidth * 0.01) / mapUnitWidth); final long tilesHigh = (long) Math .ceil((extent.getHeight() - mapUnitHeight * 0.01) / mapUnitHeight); NumberFormat nf = NumberFormat.getIntegerInstance();// so it shows grouping // for large numbers String tilesStr = nf.format(tilesWide) + " x " + nf.format(tilesHigh); return tilesStr; } @Override public void detach() { // } @Override public void setObject(String object) { // } }; tiles = new Label("tiles", tilesModel); tiles.setOutputMarkupId(true); item.add(tiles); // remove link if (TileMatrixSetEditor.this.readOnly) { removeLink = new Label("removeLink", ""); } else { removeLink = new ImageAjaxLink<Void>("removeLink", GWCIconFactory.DELETE_ICON) { private static final long serialVersionUID = 1L; @Override protected void onClick(AjaxRequestTarget target) { List<Grid> list = new ArrayList<Grid>(grids.getModelObject()); int index = ((Integer) getDefaultModelObject()).intValue(); list.remove(index); grids.setModelObject(list); target.add(container); } }; removeLink.setDefaultModel(new Model<Integer>(Integer.valueOf(index))); removeLink.add(new AttributeModifier("title", new ResourceModel( "TileMatrixSetEditor.removeLink"))); } item.add(removeLink); final boolean isResolutionsPreserved = preserveesolutionsModel.getObject(); resolution.setEnabled(isResolutionsPreserved); scale.setEnabled(!isResolutionsPreserved); resolution.add(new AjaxFormComponentUpdatingBehavior("blur") { private static final long serialVersionUID = 1L; @Override protected void onUpdate(AjaxRequestTarget target) { resolution.processInput(); Double res = resolution.getModelObject(); Double scaleDenominator = null; if (null != res) { GridSetInfo gridSetInfo = TileMatrixSetEditor.this.info.getObject(); Double metersPerUnit = gridSetInfo.getMetersPerUnit(); if (metersPerUnit != null) { scaleDenominator = res.doubleValue() * metersPerUnit.doubleValue() / GridSetFactory.DEFAULT_PIXEL_SIZE_METER; } } scale.setModelObject(scaleDenominator); target.add(resolution); target.add(scale); target.add(tiles); } }); scale.add(new AjaxFormComponentUpdatingBehavior("blur") { private static final long serialVersionUID = 1L; @Override protected void onUpdate(AjaxRequestTarget target) { scale.processInput(); final Double scaleDenominator = scale.getModelObject(); Double res = null; if (null != scaleDenominator) { GridSetInfo gridSetInfo = TileMatrixSetEditor.this.info.getObject(); final double pixelSize = gridSetInfo.getPixelSize(); Double metersPerUnit = gridSetInfo.getMetersPerUnit(); if (metersPerUnit != null) { res = pixelSize * scaleDenominator / metersPerUnit; } } resolution.setModelObject(res); target.add(resolution); target.add(scale); target.add(tiles); } }); } }; grids.setOutputMarkupId(true); // this is necessary to avoid loosing item contents on edit/validation checks grids.setReuseItems(true); table.add(grids); } private Component thLabel(String id) { Label label = new Label(id, new ResourceModel(id)); label.add(new AttributeModifier("title", new ResourceModel(id + ".title", ""))); return label; } @Override public void convertInput() { List<Grid> info = grids.getModelObject(); if (info == null || info.size() == 0) { setConvertedInput(new ArrayList<Grid>(2)); return; } setConvertedInput(info); } /** */ @Override protected void onBeforeRender() { super.onBeforeRender(); } public void addZoomLevel(ReferencedEnvelope bbox, int tileWidth, int tileHeight) { List<Grid> list = grids.getModelObject(); final Grid newGrid = new Grid(); if (list.isEmpty()) { BoundingBox extent = new BoundingBox(bbox.getMinX(), bbox.getMinY(), bbox.getMaxX(), bbox.getMaxY()); final int levels = 1; GridSet tmpGridset = GridSetFactory.createGridSet("stub", SRS.getEPSG4326(), extent, false, levels, 1D, GridSetFactory.DEFAULT_PIXEL_SIZE_METER, tileWidth, tileHeight, false); Grid grid = tmpGridset.getGridLevels()[0]; newGrid.setResolution(grid.getResolution()); newGrid.setScaleDenominator(grid.getScaleDenominator()); } else { Grid prev = list.get(list.size() - 1); newGrid.setResolution(prev.getResolution() / 2); newGrid.setScaleDenominator(prev.getScaleDenominator() / 2); } list.add(newGrid); grids.setModelObject(list); // TileMatrixSetEditor.this.convertInput(); } }