Skip to content

Commit

Permalink
First simple basic HTML output for features, inspired by GetFeatureIn…
Browse files Browse the repository at this point in the history
…fo. Still needs consistent styling, paging links, alternate format links, eventually a link to a WMS preview
  • Loading branch information
aaime committed Jun 28, 2018
1 parent ee44d73 commit cbf3d9f
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 45 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,30 +14,26 @@
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.util.HashMap; import java.util.HashMap;
import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServer;
import org.geoserver.ows.LocalWorkspace;
import org.geoserver.ows.Response; import org.geoserver.ows.Response;
import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.Operation; import org.geoserver.platform.Operation;
import org.geoserver.platform.ServiceException; import org.geoserver.platform.ServiceException;
import org.geoserver.template.GeoServerTemplateLoader;
import org.geoserver.wfs.WFSInfo; import org.geoserver.wfs.WFSInfo;
import org.geoserver.wfs3.BaseRequest; import org.geoserver.wfs3.BaseRequest;


public abstract class AbstractHTMLResponse extends Response { public abstract class AbstractHTMLResponse extends Response {


private static Configuration templateConfig = new Configuration(); private static Configuration templateConfig = new Configuration();


private final GeoServerResourceLoader resoureLoader;
private final GeoServer geoServer; private final GeoServer geoServer;
private FreemarkerTemplateSupport templateSupport;


public AbstractHTMLResponse( public AbstractHTMLResponse(
Class<?> binding, GeoServerResourceLoader loader, GeoServer geoServer) Class<?> binding, GeoServerResourceLoader loader, GeoServer geoServer) {
throws IOException {
super(binding, BaseRequest.HTML_MIME); super(binding, BaseRequest.HTML_MIME);
this.resoureLoader = loader;
this.geoServer = geoServer; this.geoServer = geoServer;
this.templateSupport = new FreemarkerTemplateSupport(loader, geoServer);
} }


@Override @Override
Expand All @@ -50,7 +46,7 @@ public void write(Object value, OutputStream output, Operation operation)
throws IOException, ServiceException { throws IOException, ServiceException {
final ResourceInfo ri = getResource(value); final ResourceInfo ri = getResource(value);
final String templateName = getTemplateName(value); final String templateName = getTemplateName(value);
Template template = getTemplate(ri, templateName); Template template = templateSupport.getTemplate(ri, templateName);


try { try {
HashMap<String, Object> model = new HashMap<>(); HashMap<String, Object> model = new HashMap<>();
Expand Down Expand Up @@ -84,29 +80,4 @@ protected String getBaseURL(Object value, Operation operation) {
* @return * @return
*/ */
protected abstract ResourceInfo getResource(Object value); protected abstract ResourceInfo getResource(Object value);
/**
* Returns the template for the specified feature type. Looking up templates is pretty
* expensive, so we cache templates by feture type and template.
*/
protected Template getTemplate(ResourceInfo resource, String templateName) throws IOException {
// otherwise, build a loader and do the lookup
GeoServerTemplateLoader templateLoader =
new GeoServerTemplateLoader(getClass(), resoureLoader);
if (resource != null) {
templateLoader.setResource(resource);
} else {
WorkspaceInfo ws = LocalWorkspace.get();
if (ws != null) {
templateLoader.setWorkspace(ws);
}
}

// Configuration is not thread safe
synchronized (templateConfig) {
templateConfig.setTemplateLoader(templateLoader);
Template t = templateConfig.getTemplate(templateName);
t.setEncoding("UTF-8");
return t;
}
}
} }
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* (c) 2018 Open Source Geospatial Foundation - all rights reserved
* * This code is licensed under the GPL 2.0 license, available at the root
* * application directory.
*
*/

/*
* (c) 2018 Open Source Geospatial Foundation - all rights reserved
* * This code is licensed under the GPL 2.0 license, available at the root
* * application directory.
*
*/
package org.geoserver.wfs3.response;

import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.IOException;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.ows.LocalWorkspace;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.template.DirectTemplateFeatureCollectionFactory;
import org.geoserver.template.FeatureWrapper;
import org.geoserver.template.GeoServerTemplateLoader;

public class FreemarkerTemplateSupport {

private static Configuration templateConfig = new Configuration();

private final GeoServerResourceLoader resoureLoader;
private final GeoServer geoServer;

static DirectTemplateFeatureCollectionFactory FC_FACTORY =
new DirectTemplateFeatureCollectionFactory();

static {
// initialize the template engine, this is static to maintain a cache of templates being
// loaded
templateConfig = new Configuration();
templateConfig.setObjectWrapper(new FeatureWrapper(FC_FACTORY));
}

public FreemarkerTemplateSupport(GeoServerResourceLoader loader, GeoServer geoServer) {
this.resoureLoader = loader;
this.geoServer = geoServer;
}

/**
* Returns the template for the specified feature type. Looking up templates is pretty
* expensive, so we cache templates by feture type and template.
*/
Template getTemplate(ResourceInfo resource, String templateName) throws IOException {
GeoServerTemplateLoader templateLoader =
new GeoServerTemplateLoader(getClass(), resoureLoader);
if (resource != null) {
templateLoader.setResource(resource);
} else {
WorkspaceInfo ws = LocalWorkspace.get();
if (ws != null) {
templateLoader.setWorkspace(ws);
}
}

// Configuration is not thread safe
synchronized (templateConfig) {
templateConfig.setTemplateLoader(templateLoader);
Template t = templateConfig.getTemplate(templateName);
t.setEncoding("UTF-8");
return t;
}
}
}
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,121 @@
/* (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.wfs3.response;

import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.List;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.Operation;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.TypeInfoCollectionWrapper;
import org.geoserver.wfs.WFSGetFeatureOutputFormat;
import org.geoserver.wfs.request.FeatureCollectionResponse;
import org.geoserver.wfs3.BaseRequest;
import org.geotools.feature.FeatureCollection;
import org.opengis.feature.simple.SimpleFeatureType;

/** Produces a Feature response in HTML. */
public class GetFeatureHTMLOutputFormat extends WFSGetFeatureOutputFormat {

private static final String FORMAT = "text/html";

private final GeoServer geoServer;
private final FreemarkerTemplateSupport templateSupport;

public GetFeatureHTMLOutputFormat(GeoServerResourceLoader loader, GeoServer geoServer) {
super(geoServer, BaseRequest.HTML_MIME);
this.geoServer = geoServer;
this.templateSupport = new FreemarkerTemplateSupport(loader, geoServer);
}

@Override
public String getMimeType(Object value, Operation operation) throws ServiceException {
return FORMAT;
}

@Override
protected void write(
FeatureCollectionResponse response, OutputStream output, Operation getFeature)
throws IOException, ServiceException {
// if there is only one feature type loaded, we allow for header/footer customization,
// otherwise we stick with the generic ones
FeatureTypeInfo referenceFeatureType = null;
List<FeatureCollection> collections = response.getFeature();
if (collections.size() == 1) {
referenceFeatureType = getResource(collections.get(0));
}

Template header = templateSupport.getTemplate(referenceFeatureType, "header.ftl");
Template footer = templateSupport.getTemplate(referenceFeatureType, "footer.ftl");

try (OutputStreamWriter osw = new OutputStreamWriter(output)) {
try {
header.process(response, osw);
} catch (TemplateException e) {
String msg = "Error occured processing header template.";
throw (IOException) new IOException(msg).initCause(e);
}

// process content template for all feature collections found
for (int i = 0; i < collections.size(); i++) {
FeatureCollection fc = collections.get(i);
if (fc != null && fc.size() > 0) {
Template content = null;
FeatureTypeInfo typeInfo = getResource(fc);
if (!(fc.getSchema() instanceof SimpleFeatureType)) {
// if there is a specific template for complex features, use that.
content = templateSupport.getTemplate(typeInfo, "complex_content.ftl");
}
if (content == null) {
content = templateSupport.getTemplate(typeInfo, "content.ftl");
}
try {
content.process(fc, osw);
} catch (TemplateException e) {
String msg =
"Error occurred processing content template "
+ content.getName()
+ " for "
+ typeInfo.prefixedName();
throw (IOException) new IOException(msg).initCause(e);
}
}
}

// if a template footer was loaded (ie, there were only one feature
// collection), process it
if (footer != null) {
try {
footer.process(response, osw);
} catch (TemplateException e) {
String msg = "Error occured processing footer template.";
throw (IOException) new IOException(msg).initCause(e);
}
}
osw.flush();
} finally {
// close any open iterators
FreemarkerTemplateSupport.FC_FACTORY.purge();
}
}

private FeatureTypeInfo getResource(FeatureCollection collection) {
FeatureTypeInfo info = null;
if (collection instanceof TypeInfoCollectionWrapper) {
info = ((TypeInfoCollectionWrapper) collection).getFeatureTypeInfo();
}
if (info == null && collection.getSchema() != null) {
info = geoServer.getCatalog().getFeatureTypeByName(collection.getSchema().getName());
}
return info;
}
}
4 changes: 4 additions & 0 deletions src/community/wfs3/src/main/resources/applicationContext.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@
<bean id="rfcGeoJsonFeaturesResponse" class="org.geoserver.wfs3.response.RFCGeoJSONFeaturesResponse"> <bean id="rfcGeoJsonFeaturesResponse" class="org.geoserver.wfs3.response.RFCGeoJSONFeaturesResponse">
<constructor-arg ref="geoServer"/> <constructor-arg ref="geoServer"/>
</bean> </bean>
<bean id="featuresHTMLResponse" class="org.geoserver.wfs3.response.GetFeatureHTMLOutputFormat">
<constructor-arg ref="resourceLoader"/>
<constructor-arg ref="geoServer"/>
</bean>


<!-- Hack to map WFS3 requests parameters to OGC KVP lookalikes --> <!-- Hack to map WFS3 requests parameters to OGC KVP lookalikes -->
<bean id="wfs3Filter" class="org.geoserver.wfs3.WFS3Filter"> <bean id="wfs3Filter" class="org.geoserver.wfs3.WFS3Filter">
Expand Down
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,67 @@
<#--
Macro's used for content
-->

<#macro property node>
<#if !node.isGeometry>
<#if node.isComplex>
<td> <@feature node=node.rawValue type=node.type /> </td>
<#else>
<td>${node.value?string}</td>
</#if>
</#if>
</#macro>

<#macro header typenode>
<caption class="featureInfo">${typenode.name}</caption>
<tr>
<th>fid</th>
<#list typenode.attributes as attribute>
<#if !attribute.isGeometry>
<#if attribute.prefix == "">
<th >${attribute.name}</th>
<#else>
<th >${attribute.prefix}:${attribute.name}</th>
</#if>
</#if>
</#list>
</tr>
</#macro>

<#macro feature node type>
<table class="featureInfo">
<@header typenode=type />
<tr>
<td>${node.fid}</td>
<#list node.attributes as attribute>
<@property node=attribute />
</#list>
</tr>
</table>
</#macro>

<#--
Body section of the GetFeatureInfo template, it's provided with one feature collection, and
will be called multiple times if there are various feature collections
-->
<table class="featureInfo">
<@header typenode=type />

<#assign odd = false>
<#list features as feature>
<#if odd>
<tr class="odd">
<#else>
<tr>
</#if>
<#assign odd = !odd>

<td>${feature.fid}</td>
<#list feature.attributes as attribute>
<@property node=attribute />
</#list>
</tr>
</#list>
</table>
<br/>

Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,35 @@
<#--
Body section of the GetFeatureInfo template, it's provided with one feature collection, and
will be called multiple times if there are various feature collections
-->
<table class="featureInfo">
<caption class="featureInfo">${type.name}</caption>
<tr>
<th>fid</th>
<#list type.attributes as attribute>
<#if !attribute.isGeometry>
<th >${attribute.name}</th>
</#if>
</#list>
</tr>

<#assign odd = false>
<#list features as feature>
<#if odd>
<tr class="odd">
<#else>
<tr>
</#if>
<#assign odd = !odd>

<td>${feature.fid}</td>
<#list feature.attributes as attribute>
<#if !attribute.isGeometry>
<td>${attribute.value?string}</td>
</#if>
</#list>
</tr>
</#list>
</table>
<br/>

Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,5 @@
<#--
Footer section of the GetFeatureInfo HTML output. Should close the body and the html tag.
-->
</body>
</html>

0 comments on commit cbf3d9f

Please sign in to comment.