package org.geowebcache.demo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringEscapeUtils;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.filter.parameters.FloatParameterFilter;
import org.geowebcache.filter.parameters.ParameterFilter;
import org.geowebcache.filter.parameters.RegexParameterFilter;
import org.geowebcache.filter.parameters.StringParameterFilter;
import org.geowebcache.grid.BoundingBox;
import org.geowebcache.grid.GridSet;
import org.geowebcache.grid.GridSetBroker;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.layer.TileLayerDispatcher;
import org.geowebcache.mime.ImageMime;
import org.geowebcache.mime.MimeType;
import org.geowebcache.mime.XMLMime;
import org.geowebcache.util.ServletUtils;
import org.springframework.util.Assert;
public class Demo {
public static void makeMap(TileLayerDispatcher tileLayerDispatcher,
GridSetBroker gridSetBroker, String action, HttpServletRequest request,
HttpServletResponse response) throws GeoWebCacheException {
String page = null;
// Do we have a layer, or should we make a list?
if (action != null) {
String layerName = ServletUtils.URLDecode(action, request.getCharacterEncoding());
TileLayer layer = tileLayerDispatcher.getTileLayer(layerName);
String rawGridSet = request.getParameter("gridSet");
String gridSetStr = null;
if (rawGridSet != null)
gridSetStr = ServletUtils.URLDecode(rawGridSet, request.getCharacterEncoding());
if (gridSetStr == null) {
gridSetStr = request.getParameter("srs");
if (gridSetStr == null) {
gridSetStr = layer.getGridSubsets().iterator().next();
}
}
String formatStr = request.getParameter("format");
if (formatStr != null) {
if (!layer.supportsFormat(formatStr)) {
throw new GeoWebCacheException("Unknow or unsupported format " + formatStr);
}
} else {
formatStr = layer.getDefaultMimeType().getFormat();
}
if (request.getPathInfo().startsWith("/demo")) {
// Running in GeoServer
page = generateHTML(layer, gridSetStr, formatStr, true);
} else {
page = generateHTML(layer, gridSetStr, formatStr, false);
}
} else {
if (request.getRequestURI().endsWith("/")) {
try {
String reqUri = request.getRequestURI();
response.sendRedirect(response.encodeRedirectURL(reqUri.substring(0,
reqUri.length() - 1)));
} catch (IOException e) {
e.printStackTrace();
}
return;
} else {
page = generateHTML(tileLayerDispatcher, gridSetBroker);
}
}
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
try {
response.getOutputStream().write(page.getBytes());
} catch (IOException ioe) {
throw new GeoWebCacheException("failed to render HTML");
}
}
private static String generateHTML(TileLayerDispatcher tileLayerDispatcher,
GridSetBroker gridSetBroker) throws GeoWebCacheException {
String reloadPath = "rest/reload";
StringBuffer buf = new StringBuffer();
buf.append("<html>\n");
buf.append(ServletUtils.gwcHtmlHeader("","GWC Demos"));
buf.append("<body>\n");
buf.append(ServletUtils.gwcHtmlLogoLink(""));
buf.append("<table cellspacing=\"10\" border=\"0\">\n"
+"<tr><td><strong>Layer name:</strong></td>\n"
+"<td><strong>Enabled:</strong></td>\n"
+"<td><strong>Grids Sets:</strong></td>\n");
buf.append("</tr>\n");
tableRows(buf, tileLayerDispatcher, gridSetBroker);
buf.append("</table>\n");
buf.append("<br />");
buf.append("<strong>These are just quick demos. GeoWebCache also supports:</strong><br />\n"
+ "<ul><li>WMTS, TMS, Virtual Earth and Google Maps</li>\n"
+ "<li>Proxying GetFeatureInfo, GetLegend and other WMS requests</li>\n"
+ "<li>Advanced request and parameter filters</li>\n"
+ "<li>Output format adjustments, such as compression level</li>\n"
+ "<li>Adjustable expiration headers and automatic cache expiration</li>\n"
+ "<li>RESTful interface for seeding and configuration (beta)</li>\n"
+ "</ul>\n"
+ "<br />\n"
+ "<strong>Reload Configuration:</strong><br />\n"
+ "<p>You can reload the configuration by pressing the following button. "
+ "The username / password is configured in WEB-INF/user.properties, or the admin "
+ " user in GeoServer if you are using the plugin.</p>\n"
+ "<form form id=\"kill\" action=\"")
.append(reloadPath)
.append("\" method=\"post\">"
+ "<input type=\"hidden\" name=\"reload_configuration\" value=\"1\" />"
+ "<span><input style=\"padding: 0; margin-bottom: -12px; border: 1;\"type=\"submit\" value=\"Reload Configuration\"></span>"
+ "</form>" + "</body></html>");
return buf.toString();
}
private static void tableRows(StringBuffer buf, TileLayerDispatcher tileLayerDispatcher,
GridSetBroker gridSetBroker) throws GeoWebCacheException {
Set<String> layerList = new TreeSet<String>(tileLayerDispatcher.getLayerNames());
for (String layerName : layerList) {
TileLayer layer = tileLayerDispatcher.getTileLayer(layerName);
if(!layer.isAdvertised()){
continue;
}
buf.append("<tr><td style=\"min-width: 100px;\"><strong>")
.append(layer.getName())
.append("</strong><br />\n");
buf.append("<a href=\"rest/seed/")
.append(layer.getName())
.append("\">Seed this layer</a>\n");
buf.append("</td><td>")
.append(layer.isEnabled())
.append("</td>");
buf.append("<td><table width=\"100%\">");
for (String gridSetId : layer.getGridSubsets()) {
GridSubset gridSubset = layer.getGridSubset(gridSetId);
String gridSetName = gridSubset.getName();
if (gridSetName.length() > 20) {
gridSetName = gridSetName.substring(0, 20) + "...";
}
buf.append("<tr><td style=\"width: 170px;\">").append(gridSetName);
buf.append("</td><td>OpenLayers: [");
buf.append(layer.getMimeTypes().stream()
.filter(type -> type.supportsTiling() || type.isVector())
.map(type -> generateDemoUrl(layer.getName(), gridSubset.getName(),type))
.collect(Collectors.joining(", ")));
buf.append("]</td><td>\n");
if (gridSubset.getName().equals(gridSetBroker.WORLD_EPSG4326.getName())) {
buf.append(" KML: [");
String prefix = "";
buf.append(layer.getMimeTypes().stream()
.filter(type-> type instanceof ImageMime || type == XMLMime.kml || type == XMLMime.kmz)
.map(type -> {
if (type == XMLMime.kmz) {
return String.format("<a href=\"%sservice/kml/%s.kml.kmz\">kmz</a>",
prefix,
layer.getName());
} else {
return String.format("<a href=\"%sservice/kml/%s.%s.kml\">%s</a>",
prefix,
layer.getName(),
type.getFileExtension(),
type.getFileExtension());
}
})
.collect(Collectors.joining(", ")));
buf.append("]");
} else {
// No Google Earth support
}
buf.append("</td></tr>");
}
buf.append("</table></td>\n");
buf.append("</tr>\n");
}
}
private static String generateDemoUrl(String layerName, String gridSetId, MimeType type) {
return String.format("<a href=\"demo/%s?gridSet=%s&format=%s\">%s</a>",
layerName,
gridSetId,
type.getFormat(),
type.getFileExtension());
}
private static String generateHTML(TileLayer layer, String gridSetStr, String formatStr,
boolean asPlugin) throws GeoWebCacheException {
String layerName = layer.getName();
GridSubset gridSubset = layer.getGridSubset(gridSetStr);
GridSet gridSet = gridSubset.getGridSet();
BoundingBox bbox = gridSubset.getGridSetBounds();
BoundingBox zoomBounds = gridSubset.getOriginalExtent();
MimeType formatMime = null;
for (MimeType mime : layer.getMimeTypes()) {
if (formatStr.equalsIgnoreCase(mime.getFormat())) {
formatMime = mime;
}
}
if (formatMime == null) {
formatMime = layer.getDefaultMimeType();
}
StringBuffer buf = new StringBuffer();
String res = "resolutions: " + Arrays.toString(gridSubset.getResolutions()) + ",\n";
String units = "units: \"" + gridSubset.getGridSet().guessMapUnits() + "\",\n";
String openLayersPath;
if (asPlugin) {
openLayersPath = "../../openlayers3/";
} else {
openLayersPath = "../openlayers3/";
}
buf.append("<html xmlns=\"http://www.w3.org/1999/xhtml\"><head>\n");
buf.append("<meta http-equiv=\"imagetoolbar\" content=\"no\">\n" + "<title>")
.append(layerName);
buf.append(" ")
.append(gridSubset.getName());
buf.append(" ")
.append(formatStr);
buf.append("</title>\n");
buf.append("<style type=\"text/css\">\n"
+ "body { font-family: sans-serif; font-weight: bold; font-size: .8em; }\n"
+ "body { border: 0px; margin: 0px; padding: 0px; }\n"
+ "#map { width: 85%; height: 85%; border: 0px; padding: 0px; }\n"
+ "#info iframe {border: none;}\n"
+ ".ol-scale-value {top: 24px; right: 8px; position: absolute; }\n"
+ "</style>\n");
buf.append("<script src=\"")
.append(openLayersPath)
.append("ol.js\"></script>\n");
buf.append("<link rel='stylesheet' href='")
.append(openLayersPath)
.append("ol.css' type='text/css'>\n");
buf.append("<script type=\"text/javascript\">\n"
+ "function init(){\n"
+ "function ScaleControl(opt_options) {\n"
+ " var options = opt_options || {};\n"
+ "\n"
+ " var element = document.createElement('div');\n"
+ " element.className = 'ol-scale-value';\n"
+ "\n"
+ " ol.control.Control.call(this, {\n"
+ " element: element,\n"
+ " target: options.target\n"
+ " });\n"
+ "\n"
+ "};\n"
+ "ol.inherits(ScaleControl, ol.control.Control);\n"
+ "ScaleControl.prototype.setMap = function(map) {\n"
+ " map.on('postrender', function() {\n"
+ " var view = map.getView();\n"
+ " var resolution = view.getResolution();\n");
buf.append(" var dpi = ")
.append(gridSubset.getDotsPerInch())
.append(";\n");
buf.append(" var mpu = map.getView().getProjection().getMetersPerUnit();\n");
buf.append(" var scale = resolution * mpu * 39.37 * dpi;\n"
+ "\n"
+ " if (scale >= 9500 && scale <= 950000) {\n"
+ " scale = Math.round(scale / 1000) + 'K';\n"
+ " } else if (scale >= 950000) {\n"
+ " scale = Math.round(scale / 1000000) + 'M';\n"
+ " } else {\n"
+ " scale = Math.round(scale);\n"
+ " } \n"
+ " this.element.innerHTML = 'Scale = 1 : ' + scale;\n"
+ " }, this);\n"
+ " ol.control.Control.prototype.setMap.call(this, map);\n"
+ "}\n"
+ "\n");
buf.append("var gridsetName = '")
.append(gridSubset.getGridSet().getName())
.append("';\n"
+ "var gridNames = ")
.append(Arrays.stream(gridSubset.getGridNames())
.map(StringEscapeUtils::escapeJavaScript)
.map(s->String.format("'%s'", s))
.collect(Collectors.joining(", ", "[", "]")))
.append(";\n"
+ "var baseUrl = '../service/wmts';\n"
+ "var style = '';\n");
buf.append("var format = '")
.append(formatStr)
.append("';\n");
buf.append("var infoFormat = 'text/html';\n");
buf.append("var layerName = '")
.append(layerName)
.append("';\n");
String unit = "";
double mpu = gridSet.getMetersPerUnit();
if (doubleEquals(mpu, 1)) {
unit = "m";
} else if (doubleEquals(mpu, 0.3048)) {
unit = "ft";
//Use the average of equatorial and polar radius, and a large margin of error
} else if (doubleEquals(mpu, Math.PI*(6378137 + 6356752)/360, Math.PI*(6378137 - 6356752)/360 )) {
unit = "degrees";
}
buf.append("var projection = new ol.proj.Projection({\n")
.append("code: '")
.append(gridSubset.getSRS().toString())
.append("',\n")
.append("units: '")
.append(unit)
.append("',\n")
.append("axisOrientation: 'neu'\n")
.append("});\n");
buf.append("var resolutions = ")
.append(Arrays.toString(gridSubset.getResolutions()))
.append(";\n");
if (formatMime.isVector()) {
buf.append("params = {\n"
+ " 'REQUEST': 'GetTile',\n"
+ " 'SERVICE': 'WMTS',\n"
+ " 'VERSION': '1.0.0',\n"
+ " 'LAYER': layerName,\n"
+ " 'STYLE': style,\n"
+ " 'TILEMATRIX': gridsetName + ':{z}',\n"
+ " 'TILEMATRIXSET': gridsetName,\n"
+ " 'FORMAT': format,\n"
+ " 'TILECOL': '{x}',\n"
+ " 'TILEROW': '{y}'\n"
+ "};\n"
+ "\n");
buf.append("function constructSource() {\n"
+ " var url = baseUrl+'?'\n"
+ " for (var param in params) {\n"
+ " url = url + param + '=' + params[param] + '&';\n"
+ " }\n"
+ " url = url.slice(0, -1);\n"
+ "\n"
+ " var source = new ol.source.VectorTile({\n"
+ " url: url,\n");
//Examine mime type for correct VT format
String vtName = formatMime.getInternalName();
if ("mapbox-vectortile".equals(vtName)) {
buf.append(" format: new ol.format.MVT({}),\n");
} else if ("topojson".equals(vtName)) {
buf.append(" format: new ol.format.TopoJSON({}),\n");
} else if ("geojson".equals(vtName)) {
buf.append(" format: new ol.format.GeoJSON({}),\n");
}
buf.append(" projection: projection,\n"
+ " tileGrid: new ol.tilegrid.WMTS({\n");
buf.append(" tileSize: [")
.append(gridSubset.getTileWidth()).append(",")
.append(gridSubset.getTileHeight()).append("],\n");
if (gridSet.isTopLeftAligned()) {
buf.append(" origin: [")
.append(bbox.getMaxX()).append(", ")
.append(bbox.getMinY()).append("],\n");
} else {
buf.append(" origin: [")
.append(bbox.getMinX()).append(", ")
.append(bbox.getMaxY()).append("],\n");
}
buf.append(" resolutions: resolutions,\n"
+ " matrixIds: gridNames\n"
+ " }),\n"
+ " wrapX: true\n"
+ " });\n"
+ " return source;\n"
+ "}\n"
+ "\n"
+ "var layer = new ol.layer.VectorTile({\n"
+ " source: constructSource()\n"
+ "});\n"
+ "\n");
} else {
buf.append("baseParams = ['VERSION','LAYER','STYLE','TILEMATRIX','TILEMATRIXSET','SERVICE','FORMAT'];\n"
+ "\n"
+ "params = {\n"
+ " 'VERSION': '1.0.0',\n"
+ " 'LAYER': layerName,\n"
+ " 'STYLE': style,\n"
+ " 'TILEMATRIX': gridNames,\n"
+ " 'TILEMATRIXSET': gridsetName,\n"
+ " 'SERVICE': 'WMTS',\n"
+ " 'FORMAT': format\n"
+ "};\n"
+ "\n");
buf.append("function constructSource() {\n"
+ " var url = baseUrl+'?'\n"
+ " for (var param in params) {\n"
+ " if (baseParams.indexOf(param.toUpperCase()) < 0) {\n"
+ " url = url + param + '=' + params[param] + '&';\n"
+ " }\n"
+ " }\n"
+ " url = url.slice(0, -1);\n"
+ "\n"
+ " var source = new ol.source.WMTS({\n"
+ " url: url,\n"
+ " layer: params['LAYER'],\n"
+ " matrixSet: params['TILEMATRIXSET'],\n"
+ " format: params['FORMAT'],\n"
+ " projection: projection,\n"
+ " tileGrid: new ol.tilegrid.WMTS({\n");
buf.append(" tileSize: [")
.append(gridSubset.getTileWidth()).append(",")
.append(gridSubset.getTileHeight()).append("],\n");
buf.append(" extent: [")
.append(bbox.getMinX()).append(",")
.append(bbox.getMinY()).append(",")
.append(bbox.getMaxX()).append(",")
.append(bbox.getMaxY()).append("],\n");
if (gridSubset.fullGridSetCoverage()) {
buf.append(" origins: [");
for (int i = 0; i < gridSet.getNumLevels(); i++) {
if (i != 0) {
buf.append(",");
}
BoundingBox subbox = gridSubset.getCoverageBounds(i);
if (gridSet.isTopLeftAligned()) {
buf.append("[")
.append(subbox.getMaxX()).append(", ")
.append(subbox.getMinY()).append("]");
} else {
buf.append("[")
.append(subbox.getMinX()).append(", ")
.append(subbox.getMaxY()).append("]");
}
}
buf.append("],\n");
} else {
if (gridSet.isTopLeftAligned()) {
buf.append(" origin: [")
.append(bbox.getMaxX()).append(", ")
.append(bbox.getMinY()).append("],\n");
} else {
buf.append(" origin: [")
.append(bbox.getMinX()).append(", ")
.append(bbox.getMaxY()).append("],\n");
}
}
buf.append(" resolutions: resolutions,\n"
+ " matrixIds: params['TILEMATRIX']\n"
+ " }),\n"
+ " style: params['STYLE'],\n"
+ " wrapX: true\n"
+ " });\n"
+ " return source;\n"
+ "}\n"
+ "\n"
+ "var layer = new ol.layer.Tile({\n"
+ " source: constructSource()\n"
+ "});\n"
+ "\n");
}
buf.append("var view = new ol.View({\n"
+ " center: [0, 0],\n"
+ " zoom: 2,\n"
+ " projection: projection,\n");
buf.append(" extent: [")
.append(bbox.toString())
.append("]\n");
buf.append("});\n"
+ "\n"
+ "var map = new ol.Map({\n"
+ " controls: ol.control.defaults({attribution: false}).extend([\n"
+ " new ol.control.MousePosition(),\n"
+ " new ScaleControl()\n"
+ " ]),\n"
+ " layers: [layer],\n"
+ " target: 'map',\n"
+ " view: view\n"
+ "});\n");
buf.append("map.getView().fit([")
.append(zoomBounds.toString())
.append("], map.getSize());\n");
buf.append("\n"
+ "window.setParam = function(name, value) {\n"
+ " if (name == \"STYLES\") {\n"
+ " name = \"STYLE\"\n"
+ " }\n"
+ " params[name] = value;\n"
+ " layer.setSource(constructSource());\n"
+ " map.updateSize();\n"
+ "} \n"
+ "\n"
+ "map.on('singleclick', function(evt) {\n"
+ " document.getElementById('info').innerHTML = '';\n"
+ "\n"
+ " var source = layer.getSource();\n"
+ " var resolution = view.getResolution();\n"
+ " var tilegrid = source.getTileGrid();\n"
+ " var tileResolutions = tilegrid.getResolutions();\n"
+ " var zoomIdx, diff = Infinity;\n"
+ "\n"
+ " for (var i = 0; i < tileResolutions.length; i++) {\n"
+ " var tileResolution = tileResolutions[i];\n"
+ " var diffP = Math.abs(resolution-tileResolution);\n"
+ " if (diffP < diff) {\n"
+ " diff = diffP;\n"
+ " zoomIdx = i;\n"
+ " }\n"
+ " if (tileResolution < resolution) {\n"
+ " break;\n"
+ " }\n"
+ " }\n"
+ " var tileSize = tilegrid.getTileSize(zoomIdx);\n"
+ " var tileOrigin = tilegrid.getOrigin(zoomIdx);\n"
+ "\n"
+ " var fx = (evt.coordinate[0] - tileOrigin[0]) / (resolution * tileSize[0]);\n"
+ " var fy = (tileOrigin[1] - evt.coordinate[1]) / (resolution * tileSize[1]);\n"
+ " var tileCol = Math.floor(fx);\n"
+ " var tileRow = Math.floor(fy);\n"
+ " var tileI = Math.floor((fx - tileCol) * tileSize[0]);\n"
+ " var tileJ = Math.floor((fy - tileRow) * tileSize[1]);\n"
+ " var matrixIds = tilegrid.getMatrixIds()[zoomIdx];\n"
+ " var matrixSet = source.getMatrixSet();\n"
+ "\n"
+ " var url = baseUrl+'?'\n"
+ " for (var param in params) {\n"
+ " if (param.toUpperCase() == 'TILEMATRIX') {\n"
+ " url = url + 'TILEMATRIX='+matrixIds+'&';\n"
+ " } else {\n"
+ " url = url + param + '=' + params[param] + '&';\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " url = url\n"
+ " + 'SERVICE=WMTS&REQUEST=GetFeatureInfo'\n"
+ " + '&INFOFORMAT=' + infoFormat\n"
+ " + '&TileCol=' + tileCol\n"
+ " + '&TileRow=' + tileRow\n"
+ " + '&I=' + tileI\n"
+ " + '&J=' + tileJ;\n"
+ "\n"
+ " if (url) {\n"
+ " document.getElementById('info').innerHTML = 'Loading... please wait...';\n"
+ " var xmlhttp = new XMLHttpRequest();"
+ " xmlhttp.onreadystatechange = function() {\n"
+ " if (xmlhttp.readyState == XMLHttpRequest.DONE ) {\n"
+ " if (xmlhttp.status == 200) {\n"
+ " document.getElementById('info').innerHTML = xmlhttp.responseText;\n"
+ " }\n"
+ " else {\n"
+ " document.getElementById('info').innerHTML = '';\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " xmlhttp.open('GET', url, true);\n"
+ " xmlhttp.send();\n"
+ " }\n"
+ "});\n"
+ "}\n");
buf.append("</script>\n" + "</head>\n" + "<body onload=\"init()\">\n");
buf.append("<div id=\"params\">")
.append(makeModifiableParameters(layer))
.append("</div>\n");
buf.append("<div id=\"map\"></div>\n" + "<div id=\"info\"></div>\n</body>\n"
+ "</html>");
return buf.toString();
}
private static String makeModifiableParameters(TileLayer tl) {
List<ParameterFilter> parameterFilters = tl.getParameterFilters();
if (parameterFilters == null || parameterFilters.size() == 0) {
return "";
}
parameterFilters = new ArrayList<ParameterFilter>(parameterFilters);
Collections.sort(parameterFilters, new Comparator<ParameterFilter>() {
public int compare(ParameterFilter o1, ParameterFilter o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
StringBuilder doc = new StringBuilder();
doc.append("Modifiable Parameters:\n");
doc.append("<table>\n");
for (ParameterFilter pf : parameterFilters) {
Assert.notNull(pf);
String key = pf.getKey();
String defaultValue = pf.getDefaultValue();
List<String> legalValues = pf.getLegalValues();
doc.append("<tr><td>").append(key.toUpperCase()).append(": ").append("</td><td>");
String parameterId = key;
if (pf instanceof StringParameterFilter) {
Map<String, String> keysValues = makeParametersMap(defaultValue, legalValues);
makePullDown(doc, parameterId, keysValues, defaultValue);
} else if (pf instanceof RegexParameterFilter) {
makeTextInput(doc, parameterId, 25);
} else if (pf instanceof FloatParameterFilter) {
FloatParameterFilter floatParam = (FloatParameterFilter) pf;
if (floatParam.getValues().isEmpty()) {
// accepts any value
makeTextInput(doc, parameterId, 25);
} else {
Map<String, String> keysValues = makeParametersMap(defaultValue, legalValues);
makePullDown(doc, parameterId, keysValues, defaultValue);
}
} else if ("org.geowebcache.filter.parameters.NaiveWMSDimensionFilter".equals(pf
.getClass().getName())) {
makeTextInput(doc, parameterId, 25);
} else {
// Unknown filter type
if (legalValues == null) {
// Doesn't have a defined set of values, just provide a text field
makeTextInput(doc, parameterId, 25);
} else {
// Does have a defined set of values, so provide a drop down
Map<String, String> keysValues = makeParametersMap(defaultValue, legalValues);
makePullDown(doc, parameterId, keysValues, defaultValue);
}
}
doc.append("</td></tr>\n");
}
doc.append("</table>\n");
return doc.toString();
}
private static Map<String, String> makeParametersMap(String defaultValue,
List<String> legalValues) {
Map<String, String> map = new TreeMap<String, String>();
for (String s : legalValues) {
map.put(s, s);
}
map.put(defaultValue, defaultValue);
return map;
}
private static void makePullDown(StringBuilder doc, String id, Map<String, String> keysValues,
String defaultKey) {
doc.append("<select name=\"" + id + "\" onchange=\"window.setParam('" + id + "', value)\">\n");
Iterator<Entry<String, String>> iter = keysValues.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
final String key = entry.getKey();
// equal, including both null
if ((key==null && defaultKey==null) || (key!=null && key.equals(defaultKey))) {
doc.append("<option value=\"" + entry.getValue() + "\" selected=\"selected\">"
+ entry.getKey() + "</option>\n");
} else {
doc.append("<option value=\"" + entry.getValue() + "\">" + entry.getKey()
+ "</option>\n");
}
}
doc.append("</select>\n");
}
private static void makeTextInput(StringBuilder doc, String id, int size) {
doc.append("<input name=\"" + id + "\" type=\"text\" size=\"" + size
+ "\" onblur=\"window.setParam('" + id + "', value)\" />\n");
}
private static boolean doubleEquals(double d1, double d2) {
return doubleEquals(d1, d2, 0);
}
private static boolean doubleEquals(double d1, double d2, double buffer) {
double diff = Math.abs(d1 - d2);
return diff < (Math.ulp(d1) + Math.ulp(d2) + buffer);
}
}