package org.openlca.geo.parameter; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.geotools.data.DataStore; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.opengis.feature.simple.SimpleFeature; import org.openlca.geo.kml.KmlFeature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.geom.TopologyException; import com.vividsolutions.jts.precision.GeometryPrecisionReducer; /** * Calculates the intersections between a KML feature of a location and the * respective shapes in an shape file. It returns a map which contains the IDs * of the intersected shapes and the respective shares of the shapes to the * total value (e.g. total intersected area). Note that the shares are related * to the intersection total and not to the total of the feature. */ class IntersectionsCalculator { private Logger log = LoggerFactory.getLogger(getClass()); private final DataStore dataStore; IntersectionsCalculator(DataStore dataStore) { this.dataStore = dataStore; } Map<String, Double> calculate(KmlFeature feature) { if (feature.type == null) return Collections.emptyMap(); Geometry geo = feature.geometry; switch (feature.type) { case POINT: String shapeId = findPointShape(geo); return shapeId == null ? Collections.emptyMap() : Collections.singletonMap(shapeId, 1d); case MULTI_POINT: return calculateMultiPoint((MultiPoint) geo); case LINE: case MULTI_LINE: return calculate(geo, new LineStringValueFetch()); case POLYGON: case MULTI_POLYGON: return calculate(geo, new PolygonValueFetch()); default: log.warn("cannot calculate shares for type {}", feature.type); return Collections.emptyMap(); } } private Map<String, Double> calculateMultiPoint(MultiPoint featureGeo) { Map<String, Double> result = new HashMap<>(); int num = featureGeo.getNumGeometries(); for (int i = 0; i < num; i++) { Geometry geo = featureGeo.getGeometryN(i); String shapeId = findPointShape(geo); if (shapeId != null) { result.put(shapeId, 1d); } } return makeRelative(result, result.size()); } private String findPointShape(Geometry feature) { try (SimpleFeatureIterator iterator = getIterator()) { while (iterator.hasNext()) { SimpleFeature shape = iterator.next(); Geometry geo = (Geometry) shape.getDefaultGeometry(); if (geo instanceof Point && geo.equalsExact(feature, 1e-6)) return shape.getID(); else if (geo.contains(feature)) return shape.getID(); } return null; } catch (Exception e) { log.error("failed to fetch point values", e); return null; } } private Map<String, Double> calculate(Geometry featureGeo, ValueFetch fetch) { double featureTotal = fetch.fetchTotal(featureGeo); if (featureTotal == 0) return Collections.emptyMap(); try (SimpleFeatureIterator iterator = getIterator()) { double total = 0; Map<String, Double> shares = new HashMap<>(); while (iterator.hasNext()) { SimpleFeature shape = iterator.next(); Geometry shapeGeo = (Geometry) shape.getDefaultGeometry(); if (shapeGeo == null) { log.warn("No default geometry found in shape " + shape.getID() + " - Skipping"); continue; } if (fetch.skip(featureGeo, shapeGeo)) continue; double value = fetch.fetchSingle(featureGeo, shapeGeo); shares.put(shape.getID(), value); total += value; if (Math.abs(total - featureTotal) < 1e-16) break; } return makeRelative(shares, total); } catch (Exception e) { log.error("failed to fetch parameters for feature", e); return null; } } private Map<String, Double> makeRelative(Map<String, Double> shares, double total) { for (String id : shares.keySet()) { double val = shares.get(id); if (total == 0) { shares.put(id, 0d); } else { shares.put(id, val / total); } } return shares; } private SimpleFeatureIterator getIterator() throws Exception { String typeName = dataStore.getTypeNames()[0]; SimpleFeatureCollection collection = dataStore.getFeatureSource( typeName).getFeatures(); return collection.features(); } private interface ValueFetch { double fetchTotal(Geometry feature); double fetchSingle(Geometry feature, Geometry shape); boolean skip(Geometry feature, Geometry shape); } private class LineStringValueFetch implements ValueFetch { @Override public double fetchTotal(Geometry feature) { return feature.getLength(); } @Override public double fetchSingle(Geometry feature, Geometry shape) { return feature.intersection(shape).getLength(); } @Override public boolean skip(Geometry feature, Geometry shape) { return !feature.crosses(shape); } } private class PolygonValueFetch implements ValueFetch { @Override public double fetchTotal(Geometry feature) { return feature.getArea(); } @Override public double fetchSingle(Geometry feature, Geometry shape) { try { return feature.intersection(shape).getArea(); } catch (TopologyException e) { // see http://tsusiatsoftware.net/jts/jts-faq/jts-faq.html#D9 log.warn("Topology exception in feature calculation, " + "reducing precision of original model", e); feature = GeometryPrecisionReducer.reduce(feature, new PrecisionModel(PrecisionModel.FLOATING_SINGLE)); return feature.intersection(shape).getArea(); } } @Override public boolean skip(Geometry feature, Geometry shape) { return !feature.intersects(shape); } } }