Skip to content

Commit

Permalink
switched to dedicated template
Browse files Browse the repository at this point in the history
  • Loading branch information
turingtestfail committed Jun 10, 2024
1 parent f8dc398 commit 1c73dd4
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
import static org.geoserver.mapml.MapMLConstants.MAPML_SKIP_STYLES_FO;
import static org.geoserver.mapml.MapMLConstants.MAPML_USE_FEATURES;
import static org.geoserver.mapml.MapMLConstants.MAPML_USE_TILES;
import static org.geoserver.mapml.template.MapMLMapTemplate.MAPML_PREVIEW_HEAD_FTL;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -48,6 +48,7 @@
import org.geoserver.mapml.tcrs.Bounds;
import org.geoserver.mapml.tcrs.Point;
import org.geoserver.mapml.tcrs.TiledCRS;
import org.geoserver.mapml.template.MapMLMapTemplate;
import org.geoserver.mapml.xml.AxisType;
import org.geoserver.mapml.xml.Base;
import org.geoserver.mapml.xml.BodyContent;
Expand Down Expand Up @@ -78,16 +79,11 @@
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.capabilities.CapabilityUtil;
import org.geoserver.wms.featureinfo.FeatureTemplate;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.api.feature.type.FeatureType;
import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.operation.TransformException;
import org.geotools.api.style.Style;
import org.geotools.data.DataUtilities;
import org.geotools.data.EmptyFeatureWriter;
import org.geotools.feature.simple.SimpleFeatureImpl;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
Expand Down Expand Up @@ -118,7 +114,7 @@ public class MapMLDocumentBuilder {
private static final int BYTES_PER_PIXEL_TRANSPARENT = 4;
private static final int BYTES_PER_KILOBYTE = 1024;
public static final String DEFAULT_MIME_TYPE = "image/png";
public static final String MAPML_PREVIEW_HEAD_FTL = "mapml-preview-head.ftl";
public static final String MAPML_XML_HEAD_FTL = "mapml-head.ftl";

private final WMS wms;

Expand Down Expand Up @@ -163,7 +159,7 @@ public class MapMLDocumentBuilder {

private Boolean isMultiExtent = MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT;

private FeatureTemplate featureTemplate = new FeatureTemplate();
private MapMLMapTemplate simpleStaticTemplate = new MapMLMapTemplate();

static {
PREVIEW_TCRS_MAP.put("OSMTILE", new TiledCRS("OSMTILE"));
Expand Down Expand Up @@ -1805,13 +1801,9 @@ private List<String> getTemplates(String templateName) {
&& mapLayerInfo.getFeature().getFeatureType()
instanceof SimpleFeatureType) {
featureType = (SimpleFeatureType) mapLayerInfo.getFeature().getFeatureType();
if (!featureTemplate.isTemplateEmpty(
if (!simpleStaticTemplate.isTemplateEmpty(
featureType, templateName, FeatureTemplate.class, "0\n")) {
// no feature is passed in so use an empty one
SimpleFeature feature = DataUtilities.template(featureType);
templates.add(
featureTemplate.template(
feature, templateName, FeatureTemplate.class));
templates.add(simpleStaticTemplate.preview(featureType));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package org.geoserver.mapml.template;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.PublishedInfo;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.template.GeoServerTemplateLoader;
import org.geoserver.template.TemplateUtils;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.featureinfo.FeatureTemplate;
import org.geotools.api.feature.simple.SimpleFeatureType;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class MapMLMapTemplate {
/** The template configuration used for placemark descriptions */
static Configuration templateConfig;

static {
// initialize the template engine, this is static to maintain a cache
templateConfig = TemplateUtils.getSafeConfiguration();

// set the default output formats for dates
templateConfig.setDateFormat("MM/dd/yyyy");
templateConfig.setDateTimeFormat("MM/dd/yyyy HH:mm:ss");
templateConfig.setTimeFormat("HH:mm:ss");

// set the default locale to be US and the
// TODO: this may be somethign we want to configure/change
templateConfig.setLocale(Locale.US);
templateConfig.setNumberFormat("0.###########");

// encoding
templateConfig.setDefaultEncoding("UTF-8");
}

/** The pattern used by DATETIME_FORMAT */
public static String DATE_FORMAT_PATTERN = "MM/dd/yy";

/** The pattern used by DATETIME_FORMAT */
public static String DATETIME_FORMAT_PATTERN = "MM/dd/yy HH:mm:ss";

/** The pattern used by DATETIME_FORMAT */
public static String TIME_FORMAT_PATTERN = "HH:mm:ss";

public static final String MAPML_PREVIEW_HEAD_FTL = "mapml-preview-head.ftl";

/** Template cache used to avoid paying the cost of template lookup for each feature */
Map<MapMLMapTemplate.TemplateKey, Template> templateCache = new HashMap<>();

/**
* Cached writer used for plain conversion from Feature to String. Improves performance
* significantly compared to an OutputStreamWriter over a ByteOutputStream.
*/
CharArrayWriter caw = new CharArrayWriter();

public void preview(Map<String, Object> model, SimpleFeatureType featureType, Writer writer)
throws IOException {
execute(model, featureType, writer, MAPML_PREVIEW_HEAD_FTL);
}

public String preview(SimpleFeatureType featureType) throws IOException {
caw.reset();
preview(Collections.emptyMap(), featureType, caw);

return caw.toString();
}

/*
* Internal helper method to exceute the template against feature or
* feature collection.
*/
private void execute(
Map<String, Object> model,
SimpleFeatureType featureType,
Writer writer,
String template)
throws IOException {

Template t = lookupTemplate(featureType, template, null);

try {
t.process(model, writer);
} catch (TemplateException e) {
String msg = "Error occured processing template.";
throw (IOException) new IOException(msg).initCause(e);
}
}

/**
* Returns the template for the specified feature type. Looking up templates is pretty
* expensive, so we cache templates by feture type and template.
*/
private Template lookupTemplate(SimpleFeatureType featureType, String template, Class<?> lookup)
throws IOException {

// lookup the cache first
TemplateKey key = new TemplateKey(featureType, template);
Template t = templateCache.get(key);
if (t != null) return t;

// otherwise, build a loader and do the lookup
GeoServerTemplateLoader templateLoader =
new GeoServerTemplateLoader(
lookup != null ? lookup : getClass(),
GeoServerExtensions.bean(GeoServerResourceLoader.class));
Catalog catalog = (Catalog) GeoServerExtensions.bean("catalog");
templateLoader.setFeatureType(catalog.getFeatureTypeByName(featureType.getName()));

// Configuration is not thread safe
synchronized (templateConfig) {
templateConfig.setTemplateLoader(templateLoader);
t = templateConfig.getTemplate(template);
}
templateCache.put(key, t);
return t;
}

/** Returns true if the required template is empty or has its default content */
public boolean isTemplateEmpty(
SimpleFeatureType featureType,
String template,
Class<FeatureTemplate> lookup,
String defaultContent)
throws IOException {
Template t = lookupTemplate(featureType, template, lookup);
if (t == null) {
return true;
}
// check if the template is empty
StringWriter sw = new StringWriter();
t.dump(sw);
// an empty template canonical form is "0\n".. weird!
String templateText = sw.toString();
return "".equals(templateText)
|| (defaultContent != null && defaultContent.equals(templateText));
}

public Map<String, String> getMapRequestElementsToModel(
String workspace,
String layersCommaDelimited,
String bbox,
String format,
String width,
String height) {
HashMap<String, String> model = new HashMap<>();
// <map-link
// href="${serviceLink(${serviceRequest},${workspace},${format},${bbox},${layers},${width},${height},${layers})}" rel="style" title="templateinsertedstyle"/>
model.put("serviceRequest", "getMap");
model.put("workspace", workspace);
model.put("format", format);
model.put("bbox", bbox);
model.put("layers", layersCommaDelimited);
model.put("width", width);
model.put("height", height);
return model;
}

private static class TemplateKey {
SimpleFeatureType type;
String template;

public TemplateKey(SimpleFeatureType type, String template) {
super();
this.type = type;
this.template = template;
}

@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((template == null) ? 0 : template.hashCode());
result = PRIME * result + ((type == null) ? 0 : type.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final MapMLMapTemplate.TemplateKey other = (MapMLMapTemplate.TemplateKey) obj;
if (template == null) {
if (other.template != null) return false;
} else if (!template.equals(other.template)) return false;
if (type == null) {
if (other.type != null) return false;
} else if (!type.equals(other.type)) return false;
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists;
import static org.geoserver.mapml.MapMLConstants.MAPML_USE_FEATURES;
import static org.geoserver.mapml.MapMLConstants.MAPML_USE_TILES;
import static org.geoserver.mapml.MapMLDocumentBuilder.MAPML_PREVIEW_HEAD_FTL;
import static org.geoserver.mapml.MapMLDocumentBuilder.MAPML_XML_HEAD_FTL;
import static org.geoserver.mapml.template.MapMLMapTemplate.MAPML_PREVIEW_HEAD_FTL;
import static org.geowebcache.grid.GridSubsetFactory.createGridSubSet;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.startsWith;
Expand Down Expand Up @@ -44,7 +45,6 @@
import javax.xml.namespace.QName;

import org.apache.commons.io.FileUtils;
import org.custommonkey.xmlunit.XMLAssert;
import org.custommonkey.xmlunit.XMLUnit;
import org.custommonkey.xmlunit.XpathEngine;
import org.geoserver.catalog.Catalog;
Expand Down Expand Up @@ -1580,6 +1580,48 @@ public void testHTMLWorkspaceQualified() throws Exception {
assertThat(layerSrc, containsString("LAYERS=Lakes"));
}

@Test
public void testXMLHeadTemplate() throws Exception {
File template = null;
try {
String layerId = getLayerId(MockData.ROAD_SEGMENTS);
FeatureTypeInfo resource =
getCatalog().getResourceByName(layerId, FeatureTypeInfo.class);
File parent = getDataDirectory().get(resource).dir();
template = new File(parent, MAPML_XML_HEAD_FTL);
FileUtils.write(
template,
"<map-style>.polygon-r1-s1{stroke-opacity:3.0; stroke-dashoffset:4; stroke-width:2.0; fill:#AAAAAA; fill-opacity:3.0; stroke:#DD0000; stroke-linecap:butt}</map-style>\n"
+ "<map-link href=\"${serviceLink(${serviceRequest},${workspace},${format},${bbox},${layers},${width},${height},${layers})}\" rel=\"style\" title=\"templateinsertedstyle\"/>",
"UTF-8");

MockRequestResponse requestResponse =
getMockRequestResponse(
MockData.ROAD_SEGMENTS.getPrefix()
+ ":"
+ MockData.ROAD_SEGMENTS.getLocalPart(),
null,
null,
"EPSG:3857",
null);
Mapml mapml = parseMapML(requestResponse);
List<Link> styleLinks = getLinkByRelType(mapml.getHead().getLinks(), RelType.STYLE);
Link templateStyleLink = styleLinks.get(0);
assertEquals("templateinsertedstyle", templateStyleLink.getTitle());
assertEquals(
"http://localhost:8080/geoserver/cite/wms?LAYERS=RoadSegments&STYLES=&FORMAT=application/xml&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&SRS=EPSG:3857&BBOX=-13885038,2870337,-7455049,6338174&WIDTH=150&HEIGHT=150&format_options=mapml:application/xml",
templateStyleLink.getHref());
String templateStyle = mapml.getHead().getStyle();
assertEquals(
".polygon-r1-s1{stroke-opacity:3.0; stroke-dashoffset:4; stroke-width:2.0; fill:#AAAAAA; fill-opacity:3.0; stroke:#DD0000; stroke-linecap:butt}",
templateStyle);
} finally {
if (template != null) {
template.delete();
}
}
}

@Test
public void testPreviewHeadTemplate() throws Exception {
File template = null;
Expand Down

0 comments on commit 1c73dd4

Please sign in to comment.