8

I'm a newbie to use GeoServer and SLDs so, sorry in advance if my question is stupid.

I'm trying to use the Point Stacker rendering transfomation for to cluster my point layer. I've a POSTGIS point layer with more than 514.000 points (all the OSM addresses in Italy ....), and so I can't to render them with a simple "red point" in my map and I can't render them using a clustering in the browser using Leaflet, OpenLayers, ecc ...

The structure of my POSTGIS layer is the following

enter image description here

If I try to use a default point symbol to render my layer the visualization is obviously slow but it works right and I can also to identify my point as I can show to you here

enter image description here

The field I'm interested on is "field_4"

I've tried to use the SLD that I've found here http://suite.opengeo.org/opengeo-docs/cartography/rt/pointstacker.html for clustering server side and it's working for mapping my layer as clustered points as I can show to you

enter image description here

I shoud add more classes in my SLD style and I'll do it, but at the moment the issue is that, using cluster, I can't identify points, not when they'are clustered (I don't want this ....), but when they are rappresented as single point. Here you are the case trying to identify the lowest triangle in the picture

enter image description here

The error is "internal error occurred Property 'count' could not be found in osm_civici": I haven't that field in my layer but I've seen that is a "field" used in the SLD style ....

NOTE: I'm using the SLD style found in OpenGeo Suite documentation at http://suite.opengeo.org/opengeo-docs/cartography/rt/pointstacker.html "as-is" and probably there is something to change but I don't know what ....

I'm using OpenGeoSuite Version 4.0.2 on Windows 7 (GeoServer 2.4-SNAPSHOT Git Revision f295d37d70c3b1f0e51942e8b031253e4d75459 Build Date 20-Jan-2014 22:29)

4 Answers 4

5

Your problem is related to Geoserver issue https://osgeo-org.atlassian.net/projects/GEOS/issues/GEOS-5228. See also discussion http://comments.gmane.org/gmane.comp.gis.geoserver.user/39166.

I understood that you want to get feature info only when user has zoomed in so much that points are not stacked together but a symbol marks exactly one point in the original data. In that case the symbol is in the same place than if you have a layer without the pointstacker SLD. Perhaps not exactly because the rendering machine can move the symbols a bit but close anyhow. In that case you should be able to use simple workaround: make your application to send GetFeatureInfo with &STYLES= pointing to a non-stacked style which may be "point" in your case. The following example shows how a GetFeatureInfo request looks like and you can see where to do the change.

http://localhost:8080/geoserver/wms?bbox=-130,24,-66,50&styles=population&format=jpeg&info_format=text/plain&request=GetFeatureInfo&layers=topp:states&query_layers=topp:states&width=550&height=250&x=170&y=160

Another workaround could be to to make a second layer "osm_civici_postgis_query" and use that only for GetFeatureRequests so that when your user clicks on the map the &layers and &query_layers parametes would automatically changed from "osm_civici_postgis" into "osm_civice_postgis_query". Because these two layer may not have the symbols exactly in the same place it might be good to add some tolerance with the "buffer" vendor option http://docs.geoserver.org/latest/en/user/services/wms/vendor.html. A large buffer may find more than one feature sometimes so I would also add for example &feature_count=10 to make the query to return more hits in such case. The default value is 1 and then user would get only one feature and that is not necessarily the closest feature from the clicked point because Geoserver does not sort the GetFeatureInfo results by the distance.

1

The solution from @Cesare didn't work for me. However I found another solution.

I added a dummy column called 'count' to the underlying view. Now I no longer get an error and results are reported back from GetFeatureInfo.

1

Your answer show me that there is a bug on GeoServer in this functionality: this is a point, bad point but a point.

I can't use your suggestions because I've to use GeoExplorer (I'm using OpenGeoSuite Version 4.0.2 on Windows 7), as client of my clustered WMS service in my solution, so I've to try some other workaround.

I've tried to check newest GeoServer versions but the problem is still present.

After some test finally I've found a solution using a new SLD that is the follow ...

I shoud exclude it when I'm at a certain scale but seems that I cant' put a something like

<?xml version="1.0" encoding="ISO-8859-1"?>
 <StyledLayerDescriptor version="1.0.0"
  xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
  xmlns="http://www.opengis.net/sld"
  xmlns:ogc="http://www.opengis.net/ogc"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <NamedLayer>
   <Name>Cluster points</Name>
   <UserStyle>
   <!-- Styles can have names, titles and abstracts -->
    <Title>Cluster points</Title>
    <Abstract>Styling using cluster points server side</Abstract>
    <FeatureTypeStyle>
      <Transformation>
        <ogc:Function name="gs:PointStacker">
          <ogc:Function name="parameter">
            <ogc:Literal>data</ogc:Literal>
          </ogc:Function>
          <ogc:Function name="parameter">
            <ogc:Literal>cellSize</ogc:Literal>
            <ogc:Literal>30</ogc:Literal>
          </ogc:Function>
          <ogc:Function name="parameter">
            <ogc:Literal>outputBBOX</ogc:Literal>
            <ogc:Function name="env">
           <ogc:Literal>wms_bbox</ogc:Literal>
            </ogc:Function>
          </ogc:Function>
          <ogc:Function name="parameter">
            <ogc:Literal>outputWidth</ogc:Literal>
            <ogc:Function name="env">
           <ogc:Literal>wms_width</ogc:Literal>
            </ogc:Function>
          </ogc:Function>
          <ogc:Function name="parameter">
            <ogc:Literal>outputHeight</ogc:Literal>
            <ogc:Function name="env">
              <ogc:Literal>wms_height</ogc:Literal>
            </ogc:Function>
          </ogc:Function>
        </ogc:Function>
      </Transformation>
     <Rule>
      <Name>rule1</Name>
      <Title>Singoli punti</Title>
      <MinScaleDenominator>2000</MinScaleDenominator>
      <PointSymbolizer>
        <Graphic>
          <Mark>
            <WellKnownName>circle</WellKnownName>
            <Fill>
              <CssParameter name="fill">#ff0000</CssParameter>
            </Fill>
          </Mark>
          <Size>6</Size>
        </Graphic>
      </PointSymbolizer>
     </Rule>
     <Rule>
        <Name>ruleGT1</Name>
        <Title>Cluster</Title>
        <ogc:Filter>
          <ogc:PropertyIsGreaterThan>
            <ogc:PropertyName>count</ogc:PropertyName>
            <ogc:Literal>1</ogc:Literal>
          </ogc:PropertyIsGreaterThan>
        </ogc:Filter>
        <MaxScaleDenominator>10000000</MaxScaleDenominator>
        <PointSymbolizer>
          <Graphic>
            <Mark>
              <WellKnownName>circle</WellKnownName>
              <Fill>
                <CssParameter name="fill">#ff0000</CssParameter>
              </Fill>
            </Mark>
            <Size>22</Size>
          </Graphic>
        </PointSymbolizer>
        <TextSymbolizer>
          <Label>
            <ogc:PropertyName>count</ogc:PropertyName>
          </Label>
          <Font>
            <CssParameter name="font-family">Arial</CssParameter>
            <CssParameter name="font-size">12</CssParameter>
            <CssParameter name="font-weight">bold</CssParameter>
          </Font>
          <LabelPlacement>
            <PointPlacement>
              <AnchorPoint>
                <AnchorPointX>0.5</AnchorPointX>
                <AnchorPointY>0.8</AnchorPointY>
              </AnchorPoint>
            </PointPlacement>
          </LabelPlacement>
          <Halo>
             <Radius>2</Radius>
             <Fill>
               <CssParameter name="fill">#ff0000</CssParameter>
               <CssParameter name="fill-opacity">0.9</CssParameter>
             </Fill>
          </Halo>
          <Fill>
            <CssParameter name="fill">#FFFFFF</CssParameter>
            <CssParameter name="fill-opacity">1.0</CssParameter>
          </Fill>
        </TextSymbolizer>
      </Rule>
    </FeatureTypeStyle>
  </UserStyle>
 </NamedLayer>
</StyledLayerDescriptor>

Here you're a sample picture showing identify on clustered poin layer using GeoExplorer

enter image description here

and another one using QGIS.

enter image description here

NOTE: in this manner, I don't know why, I'm able to identify directly point clusters too.

0

I got it decompile org.geotools.process.vector.PointStackerProcess from gt-process-feature-x.x.jar in webapps\geoserver\WEB-INF\lib and editing in the next way:

package org.geotools.process.vector;

import java.util.Collection;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.process.ProcessException;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequenceFactory;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.ProgressListener;

@DescribeProcess(title = "Point Stacker", description = "Aggregates a collection of points over a grid into one point per grid cell.")
public class PointStackerProcess implements VectorProcess {

    public static final String ATTR_GEOM = "geom";

    public static final String ATTR_COUNT = "count";

    public static final String ATTR_COUNT_UNIQUE = "countunique";

    public static final String ATTR_BOUNDING_BOX_GEOM = "geomBBOX";

    public static final String ATTR_BOUNDING_BOX = "envBBOX";

    public static final String ATTR_NORM_COUNT = "normCount";

    public static final String ATTR_NORM_COUNT_UNIQUE = "normCountUnique";

    public enum PreserveLocation {
        Single, Superimposed, Never;
    }

    @DescribeResult(name = "result", description = "Aggregated feature collection")
    public SimpleFeatureCollection execute(@DescribeParameter(name = "data", description = "Input feature collection") SimpleFeatureCollection data, @DescribeParameter(name = "cellSize", description = "Grid cell size to aggregate to, in pixels") Integer cellSize, @DescribeParameter(name = "weightClusterPosition", description = "Weight cluster position based on points added", defaultValue = "false") Boolean argWeightClusterPosition, @DescribeParameter(name = "normalize", description = "Indicates whether to add fields normalized to the range 0-1.", defaultValue = "false") Boolean argNormalize, @DescribeParameter(name = "preserveLocation", description = "Indicates wheter to preserve the original location of points for single/superimposed points", defaultValue = "Never", min = 0) PreserveLocation preserveLocation, @DescribeParameter(name = "outputBBOX", description = "Bounding box for target image extent") ReferencedEnvelope outputEnv, @DescribeParameter(name = "outputWidth", description = "Target image width in pixels", minValue = 1.0D) Integer outputWidth, @DescribeParameter(name = "outputHeight", description = "Target image height in pixels", minValue = 1.0D) Integer outputHeight, ProgressListener monitor) throws ProcessException, TransformException {
        CoordinateReferenceSystem srcCRS = ((SimpleFeatureType) data.getSchema()).getCoordinateReferenceSystem();
        CoordinateReferenceSystem dstCRS = outputEnv.getCoordinateReferenceSystem();
        MathTransform crsTransform = null;
        MathTransform invTransform = null;
        try {
            crsTransform = CRS.findMathTransform(srcCRS, dstCRS);
            invTransform = crsTransform.inverse();
        } catch (FactoryException e) {
            throw new ProcessException(e);
        }
        boolean normalize = false;
        if (argNormalize != null) {
            normalize = argNormalize.booleanValue();
        }
        boolean weightClusterPosition = false;
        if (argWeightClusterPosition != null) {
            weightClusterPosition = argWeightClusterPosition.booleanValue();
        }
        double cellSizeSrc = cellSize.intValue() * outputEnv.getWidth() / outputWidth.intValue();
        Collection<StackedPoint> stackedPts = stackPoints(data, crsTransform, cellSizeSrc, weightClusterPosition, outputEnv
                .getMinX(), outputEnv
                        .getMinY());
        SimpleFeatureType schema = null;
        for (StackedPoint sp : stackedPts) {
            schema = createType(srcCRS, normalize, sp.getFeature());
            break;
        }
        ListFeatureCollection result = new ListFeatureCollection(schema);
        SimpleFeatureBuilder fb = new SimpleFeatureBuilder(schema);
        GeometryFactory factory = new GeometryFactory((CoordinateSequenceFactory) new PackedCoordinateSequenceFactory());
        double[] srcPt = new double[2];
        double[] srcPt2 = new double[2];
        double[] dstPt = new double[2];
        double[] dstPt2 = new double[2];
        int maxCount = 0;
        int maxCountUnique = 0;
        if (normalize) {
            for (StackedPoint sp : stackedPts) {
                if (maxCount < sp.getCount()) {
                    maxCount = sp.getCount();
                }
                if (maxCountUnique < sp.getCount()) {
                    maxCountUnique = sp.getCountUnique();
                }
            }
        }

        for (StackedPoint sp : stackedPts) {
            Coordinate pt = getStackedPointLocation(preserveLocation, sp);
            srcPt[0] = pt.x;
            srcPt[1] = pt.y;
            invTransform.transform(srcPt, 0, dstPt, 0, 1);
            Coordinate psrc = new Coordinate(dstPt[0], dstPt[1]);
            Point point = factory.createPoint(psrc);
            fb.add(point);
            fb.add(Integer.valueOf(sp.getCount()));
            fb.add(Integer.valueOf(sp.getCountUnique()));
            Envelope boundingBox = sp.getBoundingBox();
            srcPt[0] = boundingBox.getMinX();
            srcPt[1] = boundingBox.getMinY();
            srcPt2[0] = boundingBox.getMaxX();
            srcPt2[1] = boundingBox.getMaxY();
            invTransform.transform(srcPt, 0, dstPt, 0, 1);
            invTransform.transform(srcPt2, 0, dstPt2, 0, 1);
            Envelope boundingBoxTransformed = new Envelope(dstPt[0], dstPt[1], dstPt2[0], dstPt2[1]);
            fb.add(boundingBoxTransformed);
            fb.add(boundingBoxTransformed.toString());

            if (normalize) {
                fb.add(Double.valueOf(sp.getCount() / maxCount));
                fb.add(Double.valueOf(sp.getCountUnique() / maxCountUnique));
            }
            if (sp.getCount() == 1) {
                //Here when count is we add the attribute value to the transformed featured
                SimpleFeature ref = sp.getFeature();
                for (AttributeDescriptor ad : ref.getType().getAttributeDescriptors()) {
                    fb.set(ad.getType().getName(), ref.getAttribute(ad.getType().getName()));
                }
            }
            result.add(fb.buildFeature(null));
        }
        return (SimpleFeatureCollection) result;
    }

    private Coordinate getStackedPointLocation(PreserveLocation preserveLocation, StackedPoint sp) {
        Coordinate pt = null;
        if (PreserveLocation.Single == preserveLocation) {
            if (sp.getCount() == 1) {
                pt = sp.getOriginalLocation();
            }
        } else if (PreserveLocation.Superimposed == preserveLocation
                && sp.getCountUnique() == 1) {
            pt = sp.getOriginalLocation();
        }
        if (pt == null) {
            pt = sp.getLocation();
        }
        return pt;
    }

    private Collection<StackedPoint> stackPoints(SimpleFeatureCollection data, MathTransform crsTransform, double cellSize, boolean weightClusterPosition, double minX, double minY) throws TransformException {
        SimpleFeatureIterator featureIt = data.features();
        Map<Coordinate, StackedPoint> stackedPts = new HashMap<>();
        double[] srcPt = new double[2];
        double[] dstPt = new double[2];
        Coordinate indexPt = new Coordinate();
        try {
            while (featureIt.hasNext()) {
                SimpleFeature feature = (SimpleFeature) featureIt.next();
                Geometry geom = (Geometry) feature.getDefaultGeometry();
                Coordinate p = getRepresentativePoint(geom);
                srcPt[0] = p.x;
                srcPt[1] = p.y;
                crsTransform.transform(srcPt, 0, dstPt, 0, 1);
                Coordinate pout = new Coordinate(dstPt[0], dstPt[1]);
                indexPt.x = pout.x;
                indexPt.y = pout.y;
                gridIndex(indexPt, cellSize);
                StackedPoint stkPt = stackedPts.get(indexPt);
                if (stkPt == null) {
                    double centreX = indexPt.x * cellSize + cellSize / 2.0D;
                    double centreY = indexPt.y * cellSize + cellSize / 2.0D;
                    stkPt = new StackedPoint(indexPt, new Coordinate(centreX, centreY));
                    stkPt.setFeature(feature);
                    stackedPts.put(stkPt.getKey(), stkPt);
                }
                stkPt.add(pout, weightClusterPosition);
            }
        } finally {
            featureIt.close();
        }
        return stackedPts.values();
    }

    private static Coordinate getRepresentativePoint(Geometry g) {
        if (g.getNumPoints() == 1) {
            return g.getCoordinate();
        }
        return g.getCentroid().getCoordinate();
    }

    private void gridIndex(Coordinate griddedPt, double cellSize) {
        long ix = (long) (griddedPt.x / cellSize);
        long iy = (long) (griddedPt.y / cellSize);
        griddedPt.x = ix;
        griddedPt.y = iy;
    }

    private SimpleFeatureType createType(CoordinateReferenceSystem crs, boolean stretch, SimpleFeature ref) {
        SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
        tb.add("geom", Point.class, crs);
        tb.add("count", Integer.class);
        tb.add("countunique", Integer.class);
        tb.add("geomBBOX", Polygon.class);
        tb.add("envBBOX", String.class);
        if (stretch) {
            tb.add("normCount", Double.class);
            tb.add("normCountUnique", Double.class);
        }
        //Here we add the attributes from the original feature
        if (ref != null) {
            for (AttributeDescriptor ad : ref.getType().getAttributeDescriptors()) {
                tb.add(ad);
            }
        }
        tb.setName("stackedPoint");
        SimpleFeatureType sfType = tb.buildFeatureType();
        return sfType;
    }

    private static class StackedPoint {


        //Here is where i save the last feature before transformation;
        private SimpleFeature feature;

        private Coordinate key;

        private Coordinate centerPt;

        private Coordinate location = null;

        private int count = 0;

        private Set<Coordinate> uniquePts;

        private Envelope boundingBox = null;

        public SimpleFeature getFeature() {
            return feature;
        }

        public void setFeature(SimpleFeature feature) {
            this.feature = feature;
        }

        public StackedPoint(Coordinate key, Coordinate centerPt) {
            this.key = new Coordinate(key);
            this.centerPt = centerPt;
        }

        public Coordinate getKey() {
            return this.key;
        }

        public Coordinate getLocation() {
            return this.location;
        }

        public int getCount() {
            return this.count;
        }

        public int getCountUnique() {
            if (this.uniquePts == null) {
                return 1;
            }
            return this.uniquePts.size();
        }

        public Envelope getBoundingBox() {
            return this.boundingBox;
        }

        public void add(Coordinate pt) {
            add(pt, false);
        }

        public void add(Coordinate pt, boolean weightClusterPosition) {
            this.count++;
            if (this.uniquePts == null) {
                this.uniquePts = new HashSet<>();
            }
            this.uniquePts.add(pt);
            if (weightClusterPosition) {
                pickWeightedLocation(pt);
            } else {
                pickNearestLocation(pt);
            }
            if (this.boundingBox == null) {
                this.boundingBox = new Envelope();
            } else {
                this.boundingBox.expandToInclude(pt);
            }
        }

        public Coordinate getOriginalLocation() {
            if (this.uniquePts != null && this.uniquePts.size() == 1) {
                return this.uniquePts.iterator().next();
            }
            return null;
        }

        private void pickWeightedLocation(Coordinate pt) {
            if (this.location == null) {
                this.location = pt;
                return;
            }
            this.location = average(this.location, pt);
        }

        private void pickNearestLocation(Coordinate pt) {
            if (this.location == null) {
                this.location = average(this.centerPt, pt);
                return;
            }
            if (pt.distance(this.centerPt) < this.location.distance(this.centerPt)) {
                this.location = average(this.centerPt, pt);
            }
        }

        private void pickCenterLocation(Coordinate pt) {
            if (this.location == null) {
                this.location = new Coordinate(pt);
                return;
            }
            this.location = this.centerPt;
        }

        private static Coordinate average(Coordinate p1, Coordinate p2) {
            double x = (p1.x + p2.x) / 2.0D;
            double y = (p1.y + p2.y) / 2.0D;
            return new Coordinate(x, y);
        }
    }
}

Then compile it again, replace in the jar file and start geoserver

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.