/* (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.geoserver.wms.georss;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.geoserver.feature.ReprojectingFeatureCollection;
import org.geoserver.wms.WMSMapContent;
import org.geotools.data.Query;
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.geometry.jts.ReferencedEnvelope;
import org.geotools.gml3.GMLConfiguration;
import org.geotools.map.Layer;
import org.geotools.referencing.CRS;
import org.geotools.xml.Configuration;
import org.geotools.xml.Encoder;
import org.geotools.xml.transform.TransformerBase;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* Base class for RSS/Atom xml transformers
*
*
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
* @author Andrea Aime - GeoSolutions
*/
public abstract class GeoRSSTransformerBase extends TransformerBase {
/** logger */
protected static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.georss");
static final Configuration GML_CONFIGURATION = new GMLConfiguration();
/**
* Enumeration for geometry encoding.
*
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*
*/
public static class GeometryEncoding {
private GeometryEncoding() {
}
public String getPrefix() {
return null;
}
public String getNamespaceURI() {
return null;
}
public void encode(Geometry g, GeoRSSTranslatorSupport translator) {
}
/**
* "simple" encoding:
*
* ex:
* <georss:point>45.256 -71.92</georss:point>,<georss:line>...</georss:line>,...
*/
public static GeometryEncoding SIMPLE = new GeometryEncoding() {
public String getPrefix() {
return "georss";
}
public String getNamespaceURI() {
return "http://www.georss.org/georss";
}
public void encode(Geometry g, GeoRSSTranslatorSupport t) {
if (g instanceof Point) {
Point p = (Point) g;
t.element("georss:point", p.getY() + " " + p.getX());
}
if (g instanceof LineString) {
LineString l = (LineString) g;
StringBuffer sb = new StringBuffer();
for (int i = 0; i < l.getNumPoints(); i++) {
Coordinate c = l.getCoordinateN(i);
sb.append(c.y).append(" ").append(c.x).append(" ");
}
sb.setLength(sb.length() - 1);
t.element("georss:line", sb.toString());
}
if (g instanceof Polygon) {
Polygon p = (Polygon) g;
LineString line = p.getExteriorRing();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < line.getNumPoints(); i++) {
Coordinate c = line.getCoordinateN(i);
sb.append(c.y).append(" ").append(c.x).append(" ");
}
sb.setLength(sb.length() - 1);
t.element("georss:polygon", sb.toString());
}
}
};
/**
* gml encoding:
*
* ex:
* <gml:Point>
* <gml:pos>45.256 -71.92</gml:pos>
* </gml:Point>
*/
public static GeometryEncoding GML = new GeometryEncoding() {
public String getPrefix() {
return "gml";
}
public String getNamespaceURI() {
return "http://www.opengis.net/gml";
}
public void encode(Geometry g, final GeoRSSTranslatorSupport translator) {
try {
// get the proper element name
QName elementName = null;
if (g instanceof Point) {
elementName = org.geotools.gml2.GML.Point;
} else if (g instanceof LineString) {
elementName = org.geotools.gml2.GML.LineString;
} else if (g instanceof Polygon) {
elementName = org.geotools.gml2.GML.Polygon;
} else if (g instanceof MultiPoint) {
elementName = org.geotools.gml2.GML.MultiPoint;
} else if (g instanceof MultiLineString) {
elementName = org.geotools.gml2.GML.MultiLineString;
} else if (g instanceof MultiPolygon) {
elementName = org.geotools.gml2.GML.MultiPolygon;
} else {
elementName = org.geotools.gml2.GML._Geometry;
}
// encode in GML3
Encoder encoder = new Encoder(GML_CONFIGURATION);
encoder.encode(g, elementName, translator);
} catch(Exception e) {
throw new RuntimeException("Cannot transform the specified geometry in GML", e);
}
};
};
/**
* lat/long encoding:
*
* ex:
* <geo:lat>45.256</geo:lat>
* <geo:long>-71.92</geo:long>
*
*/
public static GeometryEncoding LATLONG = new GeometryEncoding() {
public String getPrefix() {
return "geo";
}
public String getNamespaceURI() {
return "http://www.w3.org/2003/01/geo/wgs84_pos#";
}
public void encode(Geometry g, GeoRSSTranslatorSupport t) {
//encode the centroid
Point p = g.getCentroid();
t.element("geo:lat", "" + p.getY());
t.element("geo:long", "" + p.getX());
}
};
}
;
/**
* Geometry encoding to use.
*/
protected GeometryEncoding geometryEncoding = GeometryEncoding.LATLONG;
public void setGeometryEncoding(GeometryEncoding geometryEncoding) {
this.geometryEncoding = geometryEncoding;
}
abstract class GeoRSSTranslatorSupport extends TranslatorSupport implements ContentHandler {
public GeoRSSTranslatorSupport(ContentHandler contentHandler, String prefix, String nsURI) {
super(contentHandler, prefix, nsURI);
nsSupport.declarePrefix(geometryEncoding.getPrefix(), geometryEncoding.getNamespaceURI());
}
/**
* Encodes the geometry of a feature.
*
*/
protected void encodeGeometry(SimpleFeature feature) {
if (feature.getDefaultGeometry() != null) {
Geometry g = (Geometry) feature.getDefaultGeometry();
//handle case of multi geometry with a single geometry in it
if (g instanceof GeometryCollection) {
GeometryCollection mg = (GeometryCollection) g;
if (mg.getNumGeometries() == 1) {
g = mg.getGeometryN(0);
}
}
geometryEncoding.encode(g, this);
}
}
//overrides to increase visiblity
public void start(String element) {
super.start(element);
}
public void element(String element, String content) {
super.element(element, content);
}
@SuppressWarnings("unchecked")
protected List loadFeatureCollections(WMSMapContent map) throws IOException {
ReferencedEnvelope mapArea = map.getRenderingArea();
CoordinateReferenceSystem wgs84 = null;
FilterFactory ff = CommonFactoryFinder.getFilterFactory(GeoTools.getDefaultHints());
try {
// this should never throw an exception, but we have to deal with it anyways
wgs84 = CRS.decode("EPSG:4326");
} catch(Exception e) {
throw (IOException) (new IOException("Unable to decode WGS84...").initCause(e));
}
List featureCollections = new ArrayList();
for (Layer layer : map.layers()) {
Query query = layer.getQuery();
SimpleFeatureCollection features = null;
try {
SimpleFeatureSource source;
source = (SimpleFeatureSource) layer.getFeatureSource();
GeometryDescriptor gd = source.getSchema().getGeometryDescriptor();
if(gd == null) {
// geometryless layers...
features = source.getFeatures(query);
} else {
// make sure we are querying the source with the bbox in the right CRS, if
// not, reproject the bbox
ReferencedEnvelope env = new ReferencedEnvelope(mapArea);
CoordinateReferenceSystem sourceCRS = gd.getCoordinateReferenceSystem();
if(sourceCRS != null &&
!CRS.equalsIgnoreMetadata(mapArea.getCoordinateReferenceSystem(), sourceCRS)) {
env = env.transform(sourceCRS, true);
}
// build the mixed query
Filter original = query.getFilter();
Filter bbox = ff.bbox(gd.getLocalName(), env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY(), null);
query.setFilter(ff.and(original, bbox));
// query and eventually reproject
features = source.getFeatures(query);
if(sourceCRS != null && !CRS.equalsIgnoreMetadata(wgs84, sourceCRS)) {
ReprojectingFeatureCollection coll = new ReprojectingFeatureCollection(features, wgs84);
coll.setDefaultSource(sourceCRS);
features = coll;
}
if (features == null)
throw new NullPointerException();
featureCollections.add(features);
}
} catch (Exception e) {
String msg = "Unable to encode map layer: " + layer;
LOGGER.log(Level.SEVERE, msg, e);
}
}
return featureCollections;
}
public void characters(char[] ch, int start, int length) throws SAXException {
String string = new String(ch, start, length);
chars(string);
}
public void endElement(String uri, String localName, String qName) throws SAXException {
// working around a bug in the GML encoder, it won't properly setup the qName
end("gml:" + localName);
}
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
start(qName, atts);
}
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
if(getIndentation() > 0) {
characters(ch, start, length);
}
}
public void endDocument() throws SAXException {
// nothing to do
}
public void endPrefixMapping(String prefix) throws SAXException {
// nothing to do
}
public void processingInstruction(String target, String data) throws SAXException {
// nothing do do
}
public void setDocumentLocator(Locator locator) {
// nothing do do
}
public void skippedEntity(String name) throws SAXException {
// nothing to do
}
public void startDocument() throws SAXException {
// nothing to do
}
public void startPrefixMapping(String prefix, String uri) throws SAXException {
// nothing to do
}
}
}