7

I am trying to calculate the common "empty" area between the neighbouring polygons in order to calculate the porosity volume (the red area in the image)Example

My idea was, first to use Convert FeatureToVertices then to calculate the shortest distance between each point to the next polygon and then trying to generate the new points (blue points) and finally to generate the common polygon from these points. I could not find the way to write the code and I have only succeeded to do the first two steps. Any idea how to manage this problem?

4
  • Would you be able to edit your question to include the version and license level of ArcGIS for Desktop that you are using, please? For a question like this I suspect an Advanced level license may be advantageous.
    – PolyGeo
    Sep 15, 2014 at 13:03
  • I am using arcgis 10.1 and simply my question what is the best way to calculate the common area between polygons? thanks Sep 15, 2014 at 13:24
  • 3
    What is your criteria for neighboring? Why have you not painted other spaces red? Is the polygon in the lower right a neighbor to the other polygons? Sep 15, 2014 at 15:10
  • The criteia for neighboring is the street layer, so i need to calculate the area between the polygons that face the street (this is the final aim) and I colored few spaces just as an exapmle. Sep 16, 2014 at 12:59

1 Answer 1

4

Perhaps you could create a new polygon that encloses all others (or a sub-group of the existing polygons, e.g. those on a particular side of the road) using either the convex, or better, the concave hull and then difference the original polygons from this derived polygon. This would give you a measure of porosity.

EDIT Okay, I've written a little script in the free and open-source GIS Whitebox Geospatial Analysis Tools (available for download here) that will identify the void space between your building polygons. It works by extending the line segments that make up your building polygons, then polygonizing the group of extended line segments. It then dissolves the resulting multi-polygon and finally differences the result from the original buildings. It's not perfect, but as I mentioned in my comment, I don't think that there is a consistent rule set that can replicate what you have manually done in the question above. Still, I think the result could be satisfactory for purpose.

enter image description here

Note, the script below is written in Groovy and can be run from the Whitebox GAT scripter.

import whitebox.geospatialfiles.ShapeFile
import whitebox.geospatialfiles.shapefile.*
import whitebox.geospatialfiles.shapefile.attributes.*
import whitebox.utilities.FileUtilities
import whitebox.utilities.Topology
import com.vividsolutions.jts.operation.polygonize.Polygonizer
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineString
import com.vividsolutions.jts.geom.Geometry
import com.vividsolutions.jts.geom.GeometryCollection
import com.vividsolutions.jts.geom.GeometryFactory
import com.vividsolutions.jts.geom.MultiPolygon
import com.vividsolutions.jts.geom.Polygon
import com.vividsolutions.jts.operation.polygonize.Polygonizer
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;

com.vividsolutions.jts.geom.Geometry g
com.vividsolutions.jts.geom.Geometry buildings
double x1, x2, stX, endX, y1, y2, stY, endY, slope
GeometryFactory factory = new GeometryFactory();
Polygonizer polygonizer = new Polygonizer();

// Change the following three values as needed
String inputFile = pluginHost.getWorkingDirectory() + "building footprints.shp"
String outputFile = pluginHost.getWorkingDirectory() + "tmp5.shp"
int extensionDistance = 70

ShapeFile input = new ShapeFile(inputFile)
ArrayList<com.vividsolutions.jts.geom.Polygon> buildingPolygonList = new ArrayList<>();
ArrayList<com.vividsolutions.jts.geom.Geometry> inputGeometryList = new ArrayList<>();
boolean firstFeature = true
for (ShapeFileRecord record : input.records) {
    double[][] points = record.getGeometry().getPoints()
    recJTSGeometries = record.getGeometry().getJTSGeometries()
    for (Geometry k : recJTSGeometries) {
        buildingPolygonList.add((com.vividsolutions.jts.geom.Polygon)k)
    }
    for (int p = 0; p < points.length - 1; p++) {
        x1 = points[p][0]
        y1 = points[p][1]
        x2 = points[p + 1][0]
        y2 = points[p + 1][1]

        // Extend this line segment on both sides by extensionDistance
        if (x1 - x2 != 0) {
            slope = Math.atan2((y2 - y1) , (x2 - x1))
            xSt = x1 - extensionDistance * Math.cos(slope)
            ySt = y1 - extensionDistance * Math.sin(slope)
            xEnd = x2 + extensionDistance * Math.cos(slope)
            yEnd = y2 + extensionDistance * Math.sin(slope)
        } else {
            xSt = x1
            xEnd = x2
            if (y2 > y1) {
                ySt = y1 - extensionDistance
            } else {
                ySt = y1 + extensionDistance
            }
        }
        CoordinateArraySequence coordArray = new CoordinateArraySequence(2);
        coordArray.setOrdinate(0, 0, xSt);
        coordArray.setOrdinate(0, 1, ySt);
        coordArray.setOrdinate(1, 0, xEnd);
        coordArray.setOrdinate(1, 1, yEnd);

        if (!firstFeature) {
            inputGeometryList.add(factory.createLineString(coordArray))
        } else {
            g = factory.createLineString(coordArray)
            firstFeature = false
        }
    }
}

// make the buildings geometry
com.vividsolutions.jts.geom.Polygon[] polygonArray = new com.vividsolutions.jts.geom.Polygon[buildingPolygonList.size()];
for (i = 0; i < buildingPolygonList.size(); i++) {
    polygonArray[i] = buildingPolygonList.get(i);
}

buildings = factory.createMultiPolygon(polygonArray);
buildings = buildings.buffer(0) //corrects the topology in layer

g = g.union(factory.buildGeometry(inputGeometryList))
polygonizer.add(g)
ArrayList<com.vividsolutions.jts.geom.Polygon> outputPoly = (ArrayList)polygonizer.getPolygons()

int FID = 0
if (outputPoly.size() > 0) {
    // perform a dissolve on the resulting polygons
    polygonArray = new com.vividsolutions.jts.geom.Polygon[outputPoly.size()];
    for (i = 0; i < outputPoly.size(); i++) {
        polygonArray[i] = outputPoly.get(i);
    }

    geometriesToBuffer = factory.createMultiPolygon(polygonArray);

    Geometry buffer = geometriesToBuffer.buffer(0)

    buffer = buffer.difference(buildings)

    // output the file
    DBFField[] fields = new DBFField[1];

    fields[0] = new DBFField();
    fields[0].setName("FID");
    fields[0].setDataType(DBFField.DBFDataType.NUMERIC);
    fields[0].setFieldLength(10);
    fields[0].setDecimalCount(0);

    ShapeFile output = new ShapeFile(outputFile, ShapeType.POLYGON, fields);

    if (buffer instanceof com.vividsolutions.jts.geom.MultiPolygon) {
        MultiPolygon mpBuffer = (MultiPolygon) buffer;
        FID = 0;
        n = 0;
        for (int a = 0; a < mpBuffer.getNumGeometries(); a++) {
            g = mpBuffer.getGeometryN(a);
            if (g instanceof com.vividsolutions.jts.geom.Polygon) {
                Polygon poly = (Polygon)(g)
                ArrayList<ShapefilePoint> pnts = new ArrayList<>();

                int[] parts = new int[poly.getNumInteriorRing() + 1];

                Coordinate[] coords = poly.getExteriorRing().getCoordinates();
                if (Topology.isClockwisePolygon(coords)) {
                    for (i = 0; i < coords.length; i++) {
                        pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
                    }
                } else {
                    for (i = coords.length - 1; i >= 0; i--) {
                        pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
                    }
                }

                for (int b = 0; b < poly.getNumInteriorRing(); b++) {
                    parts[b + 1] = pnts.size();
                    coords = poly.getInteriorRingN(b).getCoordinates();
                    if (Topology.isClockwisePolygon(coords)) {
                        for (i = coords.length - 1; i >= 0; i--) {
                            pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
                        }
                    } else {
                        for (i = 0; i < coords.length; i++) {
                            pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
                        }
                    }
                }

                PointsList pl = new PointsList(pnts);
                whitebox.geospatialfiles.shapefile.Geometry wbGeometry = new whitebox.geospatialfiles.shapefile.Polygon(parts, pl.getPointsArray());
                FID++;
                Object[] rowData = new Object[1];
                rowData[0] = new Double(FID);
                output.addRecord(wbGeometry, rowData);
            }
        }
    } else if (buffer instanceof com.vividsolutions.jts.geom.Polygon) {
        Polygon poly = (Polygon)(buffer)
        ArrayList<ShapefilePoint> pnts = new ArrayList<>();

        int[] parts = new int[poly.getNumInteriorRing() + 1];

        Coordinate[] coords = poly.getExteriorRing().getCoordinates();
        if (Topology.isClockwisePolygon(coords)) {
            for (i = 0; i < coords.length; i++) {
                pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
            }
        } else {
            for (i = coords.length - 1; i >= 0; i--) {
                pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
            }
        }

        for (int b = 0; b < poly.getNumInteriorRing(); b++) {
            parts[b + 1] = pnts.size();
            coords = poly.getInteriorRingN(b).getCoordinates();
            if (Topology.isClockwisePolygon(coords)) {
                for (i = coords.length - 1; i >= 0; i--) {
                    pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
                }
            } else {
                for (i = 0; i < coords.length; i++) {
                    pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
                }
            }
        }

        PointsList pl = new PointsList(pnts);
        whitebox.geospatialfiles.shapefile.Geometry wbGeometry = new whitebox.geospatialfiles.shapefile.Polygon(parts, pl.getPointsArray());
        FID++;
        Object[] rowData = new Object[1];
        rowData[0] = new Double(FID);
        output.addRecord(wbGeometry, rowData);
    }

    output.write()

    // display the output image
    pluginHost.returnData(outputFile)

    println("Operation complete")
} else {
    println("Something went wrong.")
}

The script isn't pretty, and I'm sure I could have cleaned it up considerably, but it works.

10
  • 1
    I was thinking along the same lines (no pun intended) but looking at the image there seems to be some decision making going by on selecting the shortest side. I found myself asking which edge defines the red area. Hmmm
    – Hornbydd
    Sep 15, 2014 at 18:06
  • 2
    @Hornbydd, I thought the same, but the problem is that their method for selecting sides seems to be somewhat arbitrary. At least a concave hull would give you an objective, reproducible and transferable method of determining the inter-building area. And the concave hull, with fewer free parameters, would yield a very objective method of determining the area between buildings. Sep 15, 2014 at 18:08
  • 2
    +1 Impressive. An alternative, in the same spirit but perhaps more easily carried out (and a little more flexible) is first to buffer all polygons by slightly more than one-half the maximum inter-polygon distance that needs to be filled in. Merge the buffers, then buffer by the negative of that distance. This replaces the concave hull in your solution. It would be much more effective when processing, say, more than one side of one street of parcels at once.
    – whuber
    Sep 15, 2014 at 21:08
  • @whuber Yours sounds like a good solution too. It would be interesting to try it out. Actually the script doesn't use a concave hull solution, instead using a line-segment extension method. The reason for this change was exactly as you said, so that you wouldn't need to process one side of the street at a time. Sep 15, 2014 at 21:11
  • Actually, I have no idea about Whitbox GAT but I have installed it and opened the scripter and I have copied your code and tried to run it after changing the input and output shp file like: String inputFile = pluginHost.getWorkingDirectory() + "buildings.shp" String outputFile = pluginHost.getWorkingDirectory() + "tmp5.shp" int extensionDistance = 70..... but then i got this message: SyntaxError: mismatched input '*' expecting set null in <script> at line number 2 at column number 42..... So please where is the wrong? Sep 16, 2014 at 12:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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