/* (c) 2015 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.wms.vector;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.geotools.renderer.lite.VectorMapRenderUtils.getStyleQuery;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.MapProducerCapabilities;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.WebMap;
import org.geoserver.wms.map.AbstractMapOutputFormat;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.geotools.util.logging.Logging;
import org.opengis.feature.Attribute;
import org.opengis.feature.ComplexAttribute;
import org.opengis.feature.Feature;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.Property;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.google.common.base.Stopwatch;
import com.vividsolutions.jts.geom.Geometry;
public class VectorTileMapOutputFormat extends AbstractMapOutputFormat {
/** A logger for this class. */
private static final Logger LOGGER = Logging.getLogger(VectorTileMapOutputFormat.class);
/** WMS Service configuration * */
private final WMS wms;
private final VectorTileBuilderFactory tileBuilderFactory;
private boolean clipToMapBounds;
private double overSamplingFactor = 2.0; // 1=no oversampling, 4=four time oversample (generialization will be 1/4 pixel)
private boolean transformToScreenCoordinates;
public VectorTileMapOutputFormat(WMS wms, VectorTileBuilderFactory tileBuilderFactory) {
super(tileBuilderFactory.getMimeType(), tileBuilderFactory.getOutputFormats());
this.wms = wms;
this.tileBuilderFactory = tileBuilderFactory;
}
public void setOverSamplingFactor(double factor) {
this.overSamplingFactor = factor;
}
public void setClipToMapBounds(boolean clip) {
this.clipToMapBounds = clip;
}
public void setTransformToScreenCoordinates(boolean useScreenCoords) {
this.transformToScreenCoordinates = useScreenCoords;
}
@Override
public WebMap produceMap(final WMSMapContent mapContent) throws ServiceException, IOException {
checkNotNull(mapContent);
checkNotNull(mapContent.getRenderingArea());
checkArgument(mapContent.getMapWidth() > 0);
checkArgument(mapContent.getMapHeight() > 0);
// mapContent.setMapWidth(5 * mapContent.getMapWidth());
// mapContent.setMapHeight(5 * mapContent.getMapHeight());
final ReferencedEnvelope renderingArea = mapContent.getRenderingArea();
final Rectangle paintArea = new Rectangle(mapContent.getMapWidth(),
mapContent.getMapHeight());
VectorTileBuilder vectorTileBuilder;
vectorTileBuilder = this.tileBuilderFactory.newBuilder(paintArea, renderingArea);
CoordinateReferenceSystem sourceCrs;
for (Layer layer : mapContent.layers()) {
FeatureSource<?, ?> featureSource = layer.getFeatureSource();
GeometryDescriptor geometryDescriptor = featureSource.getSchema()
.getGeometryDescriptor();
if (null == geometryDescriptor) {
continue;
}
sourceCrs = geometryDescriptor.getType().getCoordinateReferenceSystem();
PipelineBuilder builder;
try {
builder = PipelineBuilder.newBuilder(renderingArea, paintArea, sourceCrs,
overSamplingFactor);
} catch (FactoryException e) {
throw new ServiceException(e);
}
Pipeline pipeline = builder.preprocess().transform(transformToScreenCoordinates)
.simplify(transformToScreenCoordinates)
.clip(clipToMapBounds, transformToScreenCoordinates).collapseCollections()
.build();
Query query = getStyleQuery(layer, mapContent);
query.getHints().remove(Hints.SCREENMAP);
FeatureCollection<?, ?> features = featureSource.getFeatures(query);
Feature feature;
Stopwatch sw = Stopwatch.createStarted();
int count = 0;
int total = 0;
try (FeatureIterator<?> it = features.features()) {
while (it.hasNext()) {
feature = it.next();
total++;
Geometry originalGeom;
Geometry finalGeom;
originalGeom = (Geometry) feature.getDefaultGeometryProperty().getValue();
try {
finalGeom = pipeline.execute(originalGeom);
} catch (Exception processingException) {
processingException.printStackTrace();
continue;
}
if (finalGeom.isEmpty()) {
continue;
}
final String layerName = feature.getName().getLocalPart();
final String featureId = feature.getIdentifier().toString();
final String geometryName = geometryDescriptor.getName().getLocalPart();
final Map<String, Object> properties = getProperties(feature);
vectorTileBuilder.addFeature(layerName, featureId, geometryName, finalGeom,
properties);
count++;
}
}
sw.stop();
if (LOGGER.isLoggable(Level.FINE)) {
String msg = String.format("Added %,d out of %,d features of '%s' in %s", count,
total, layer.getTitle(), sw);
// System.err.println(msg);
LOGGER.fine(msg);
}
}
WebMap map = vectorTileBuilder.build(mapContent);
return map;
}
private Map<String, Object> getProperties(ComplexAttribute feature) {
Map<String, Object> props = new TreeMap<>();
for (Property p : feature.getProperties()) {
if (!(p instanceof Attribute) || (p instanceof GeometryAttribute)) {
continue;
}
String name = p.getName().getLocalPart();
Object value;
if (p instanceof ComplexAttribute) {
value = getProperties((ComplexAttribute) p);
} else {
value = p.getValue();
}
if (value != null) {
props.put(name, value);
}
}
return props;
}
/**
* @return {@code null}, not a raster format.
*/
@Override
public MapProducerCapabilities getCapabilities(String format) {
return null;
}
}