/* (c) 2014 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.vfny.geoserver.wms.responses.map.htmlimagemap;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.WebMap;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultQuery;
import org.geotools.data.Query;
import org.geotools.data.crs.ReprojectFeatureResults;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureTypes;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.spatial.BBOX;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
/**
* Encodes a set of MapLayers in HTMLImageMap format.
*
* @author Mauro Bartolomeoli
*/
public class EncodeHTMLImageMap extends WebMap{
private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.vfny.geoserver.responses.wms.map");
/** Filter factory for creating filters */
private final static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints());
/**
* Current writer.
* The writer is able to encode a single feature.
*/
private HTMLImageMapWriter writer;
private final int maxFilterSize=15;
/**
* Creates a new EncodeHTMLImageMap object.
*
* @param mapContent current wms context
*/
public EncodeHTMLImageMap(WMSMapContent mapContent) {
super(mapContent);
}
/**
* Encodes the current set of layers.
*
* @param out stream to write the produced map to.
*
* @throws IOException if an error occurs in encoding map
*/
public void encode(final OutputStream out) throws IOException {
// initializes the writer
this.writer = new HTMLImageMapWriter(out, mapContent);
long t = System.currentTimeMillis();
try {
// encodes the different layers
writeLayers();
this.writer.flush();
t = System.currentTimeMillis() - t;
LOGGER.info("HTML ImageMap generated in " + t + " ms");
} catch (AbortedException ex) {
return;
}
}
/**
* Applies Filters from style rules to the given query, to optimize
* DataStore queries.
* Similar to the method in StreamingRenderer.
*
* @param styles
* @param q
*/
private Filter processRuleForQuery(FeatureTypeStyle[] styles) {
try {
// first we check to see if there are >
// "getMaxFiltersToSendToDatastore" rules
// if so, then we dont do anything since no matter what there's too
// many to send down.
// next we check for any else rules. If we find any --> dont send
// anything to Datastore
// next we check for rules w/o filters. If we find any --> dont send
// anything to Datastore
//
// otherwise, we're gold and can "or" together all the fiters then
// AND it with the original filter.
// ie. SELECT * FROM ... WHERE (the_geom && BBOX) AND (filter1 OR
// filter2 OR filter3);
final List<Filter> filtersToDS = new ArrayList<Filter>();
final int stylesLength = styles.length;
int styleRulesLength;
FeatureTypeStyle style;
int u = 0;
Rule r;
for (int t = 0; t < stylesLength; t++) // look at each
// featuretypestyle
{
style = styles[t];
Rule[] rules=style.getRules();
styleRulesLength = rules.length;
for (u = 0; u < styleRulesLength; u++) // look at each
// rule in the
// featuretypestyle
{
r = rules[u];
if (r.getFilter() == null)
return null; // uh-oh has no filter (want all rows)
if(r.hasElseFilter())
return null; // uh-oh has elseRule
filtersToDS.add(r.getFilter());
}
}
Filter ruleFiltersCombined=null;
Filter newFilter;
// We're GOLD -- OR together all the Rule's Filters
if (filtersToDS.size() == 1) // special case of 1 filter
{
ruleFiltersCombined = filtersToDS.get(0);
// OR all filters if they are under maxFilterSize in number, else, do not filter
} else if(filtersToDS.size()<maxFilterSize) {
// build it up
ruleFiltersCombined = filtersToDS.get(0);
final int size = filtersToDS.size();
for (int t = 1; t < size; t++) // NOTE: dont
// redo 1st one
{
newFilter = filtersToDS.get(t);
ruleFiltersCombined = filterFactory.or(
ruleFiltersCombined, newFilter);
}
}
return ruleFiltersCombined;
/*
// combine with the geometry filter (preexisting)
ruleFiltersCombined = filterFactory.or(
q.getFilter(), ruleFiltersCombined);
// set the actual filter
q.setFilter(ruleFiltersCombined);
*/
} catch (Exception e) {
return null;
}
}
/**
* Filters the feature type styles of <code>style</code> returning only
* those that apply to <code>featureType</code>
* <p>
* This methods returns feature types for which
* <code>featureTypeStyle.getFeatureTypeName()</code> matches the name
* of the feature type of <code>featureType</code>, or matches the name of
* any parent type of the feature type of <code>featureType</code>. This
* method returns an empty array in the case of which no rules match.
* </p>
* @param style The style containing the feature type styles.
* @param featureType The feature type being filtered against.
*
*/
protected FeatureTypeStyle[] filterFeatureTypeStyles(Style style, SimpleFeatureType featureType) {
FeatureTypeStyle[] featureTypeStyles = style.getFeatureTypeStyles();
if ((featureTypeStyles == null) || (featureTypeStyles.length == 0)) {
return new FeatureTypeStyle[0];
}
List<FeatureTypeStyle> filtered = new ArrayList<FeatureTypeStyle>(featureTypeStyles.length);
for (int i = 0; i < featureTypeStyles.length; i++) {
FeatureTypeStyle featureTypeStyle = featureTypeStyles[i];
String featureTypeName = featureTypeStyle.getFeatureTypeName();
Rule[] rules=featureTypeStyle.getRules();
if(rules!=null)
rules=filterRules(rules);
//does this style have any rules
if (rules == null || rules.length == 0 ) {
continue;
}
featureTypeStyle.setRules(rules);
//does this style apply to the feature collection
if (featureType.getTypeName().equalsIgnoreCase(featureTypeName)
|| FeatureTypes.isDecendedFrom(featureType,null,featureTypeName)) {
filtered.add(featureTypeStyle);
}
}
return filtered.toArray(new FeatureTypeStyle[filtered.size()]);
}
/**
* Evaluates if the supplied scaleDenominator is congruent with a rule defined scale range.
* @param r current rule
* @param scaleDenominator current value to verify
* @return true if scaleDenominator is in the rule defined range
*/
public static boolean isWithInScale(Rule r,double scaleDenominator) {
return ((r.getMinScaleDenominator() ) <= scaleDenominator)
&& ((r.getMaxScaleDenominator()) > scaleDenominator);
}
/**
* Filter given rules, to consider only the rules compatible
* with the current scale.
* @param rules
*
*/
private Rule[] filterRules(Rule[] rules) {
List<Rule> result=new ArrayList<Rule>();
for(int count=0;count<rules.length;count++) {
Rule rule=rules[count];
double scaleDenominator;
try {
scaleDenominator = RendererUtilities.calculateScale(mapContent.getRenderingArea(), mapContent.getMapWidth(), mapContent.getMapHeight(),90);
//is this rule within scale?
if (EncodeHTMLImageMap.isWithInScale(rule,scaleDenominator)) {
result.add(rule);
}
} catch (TransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FactoryException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// TODO Auto-generated method stub
return result.toArray(new Rule[result.size()]);
}
/**
* Encodes the current set of layers.
*
* @throws IOException if an error occurs during encoding
* @throws AbortedException if the encoding is aborted
*
* @task TODO: respect layer filtering given by their Styles
*/
@SuppressWarnings("unchecked")
private void writeLayers() throws IOException, AbortedException {
for(Layer layer:mapContent.layers()){
SimpleFeatureSource fSource;
fSource = (SimpleFeatureSource) layer.getFeatureSource();
SimpleFeatureType schema = fSource.getSchema();
/*FeatureSource fSource = layer.getFeatureSource();
FeatureType schema = fSource.getSchema();*/
try {
ReferencedEnvelope aoi = mapContent.getRenderingArea();
CoordinateReferenceSystem sourceCrs = schema.getGeometryDescriptor().getCoordinateReferenceSystem();
boolean reproject = (sourceCrs != null)
&& !CRS.equalsIgnoreMetadata(aoi.getCoordinateReferenceSystem(), sourceCrs);
if (reproject) {
aoi = aoi.transform(sourceCrs, true);
}
// apply filters.
// 1) bbox filter
BBOX bboxFilter = filterFactory.bbox(schema.getGeometryDescriptor().getLocalName(),
aoi.getMinX() , aoi.getMinY(), aoi.getMaxX(), aoi.getMaxY(), null);
Query q = new Query(schema.getTypeName(), bboxFilter);
String mapId = null;
mapId = schema.getTypeName();
writer.write("<map name=\"" + mapId + "\">\n");
// 2) definition query filter
Query definitionQuery = layer.getQuery();
LOGGER.info("Definition Query: "+definitionQuery.toString());
if (!definitionQuery.equals(Query.ALL)) {
if (q.equals(Query.ALL)) {
q = (Query) definitionQuery;
} else {
q = (Query) DataUtilities.mixQueries(definitionQuery, q, "HTMLImageMapEncoder");
}
}
FeatureTypeStyle[] ftsList=filterFeatureTypeStyles(layer.getStyle(), fSource.getSchema());
// 3) rule filters
Filter ruleFilter=processRuleForQuery(ftsList);
if(ruleFilter!=null) {
// combine with the geometry filter (preexisting)
ruleFilter = filterFactory.and(
q.getFilter(), ruleFilter);
// set the actual filter
//q.setFilter(ruleFilter);
q = new DefaultQuery(schema.getTypeName(),ruleFilter);
//q = (Query) DataUtilities.mixQueries(new Query(schema.getTypeName(),ruleFilter), q, "HTMLImageMapEncoder");
}
//ensure reprojection occurs, do not trust query, use the wrapper
SimpleFeatureCollection fColl = null;//fSource.getFeatures(q);
//FeatureCollection fColl=null;
if ( reproject ) {
fColl=new ReprojectFeatureResults( fSource.getFeatures(q),mapContent.getCoordinateReferenceSystem() );
} else
fColl=fSource.getFeatures(q);
// encodes the current layer, using the defined style
writer.writeFeatures(fColl, ftsList);
writer.write("</map>\n");
} catch (IOException ex) {
throw ex;
} catch (AbortedException ae) {
LOGGER.info("process aborted: " + ae.getMessage());
throw ae;
} catch (Throwable t) {
LOGGER.warning("UNCAUGHT exception: " + t.getMessage());
IOException ioe = new IOException("UNCAUGHT exception: " + t.getMessage());
ioe.setStackTrace(t.getStackTrace());
throw ioe;
}
}
}
}