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.

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.