package org.openlca.geo.kml; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import org.geotools.kml.KMLConfiguration; import org.geotools.xml.Parser; import org.opengis.feature.simple.SimpleFeature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; public class KmlFeature { public final String kml; public final Geometry geometry; public final FeatureType type; KmlFeature(String kml, Geometry geometry, FeatureType type) { this.kml = kml; this.geometry = geometry; this.type = type; } public static KmlFeature empty() { return new KmlFeature(null, null, FeatureType.EMPTY); } public static KmlFeature parse(String kml) throws Exception { KMLConfiguration configuration = new KMLConfiguration(); Parser parser = new Parser(configuration); StringReader reader = new StringReader(kml); SimpleFeature root = (SimpleFeature) parser.parse(reader); Object featureObj = root.getAttribute("Feature"); if (!(featureObj instanceof List)) return empty(); List<?> featureList = (List<?>) featureObj; if (featureList.isEmpty()) return empty(); List<Geometry> geometries = new ArrayList<Geometry>(); for (Object obj : featureList) { SimpleFeature feature = (SimpleFeature) obj; Geometry geometry = (Geometry) feature.getAttribute("Geometry"); geometries.add(geometry); } Geometry geometry = merge(geometries); FeatureType type = getType(geometry); return new KmlFeature(kml, geometry, type); } private static Geometry merge(List<Geometry> geometries) { if (geometries == null || geometries.size() == 0) return null; List<Geometry> toMerge = new ArrayList<Geometry>(); for (Geometry geometry : geometries) toMerge.addAll(collectSingleGeometries(geometry)); if (toMerge.size() == 1) return toMerge.get(0); FeatureType type = getCollectionType(toMerge); if (type == null) // mixed content toMerge = filterByFirstType(toMerge); type = getCollectionType(toMerge); return createCollection(type, toMerge); } private static Geometry createCollection(FeatureType type, List<Geometry> toMerge) { GeometryFactory factory = new GeometryFactory(); if (type == FeatureType.LINE) return factory.createMultiLineString(GeometryFactory .toLineStringArray(toMerge)); if (type == FeatureType.POINT) return factory.createMultiPoint(GeometryFactory .toPointArray(toMerge)); if (type == FeatureType.POLYGON) return factory.createMultiPolygon(GeometryFactory .toPolygonArray(toMerge)); return null; } private static List<Geometry> filterByFirstType(List<Geometry> toMerge) { FeatureType type = null; List<Geometry> filtered = new ArrayList<>(); for (Geometry geometry : toMerge) { FeatureType current = getType(geometry); if (type == null) type = current; if (type != current) continue; filtered.add(geometry); } return filtered; } private static FeatureType getCollectionType(List<Geometry> geometries) { FeatureType type = null; for (Geometry geometry : geometries) { if (type == null) // first geometry type type = getType(geometry); else if (type == getType(geometry)) // same type as first continue; else return null; // mixed type, return null } return type; } private static List<Geometry> collectSingleGeometries(Geometry geometry) { List<Geometry> collected = new ArrayList<Geometry>(); if (isSingleGeometry(geometry)) { collected.add(geometry); return collected; } for (int n = 0; n < geometry.getNumGeometries(); n++) { collected.addAll(collectSingleGeometries(geometry.getGeometryN(n))); } return collected; } private static boolean isSingleGeometry(Geometry geometry) { FeatureType type = getType(geometry); if (type.isMulti()) return false; return true; } private static FeatureType getType(Geometry geometry) { if (geometry == null) return FeatureType.EMPTY; String typeString = geometry.getGeometryType(); if (typeString == null) return FeatureType.EMPTY; switch (typeString) { case "LineString": return FeatureType.LINE; case "Point": return FeatureType.POINT; case "Polygon": return FeatureType.POLYGON; case "MultiLineString": return FeatureType.MULTI_LINE; case "MultiPoint": return FeatureType.MULTI_POINT; case "MultiPolygon": return FeatureType.MULTI_POLYGON; case "GeometryCollection": return FeatureType.MULTI_GEOMETRY; default: Logger log = LoggerFactory.getLogger(KmlFeature.class); log.warn("unknown geometry {}; set type as empty", typeString); return null; } } }