/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.importer.format; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.io.FilenameUtils; import org.geoserver.catalog.AttributeTypeInfo; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogBuilder; import org.geoserver.catalog.CatalogFactory; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.MetadataMap; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.importer.ImportData; import org.geoserver.importer.ImportTask; import org.geoserver.importer.VectorFormat; import org.geoserver.importer.job.ProgressMonitor; import org.geoserver.importer.transform.KMLPlacemarkTransform; import org.geotools.data.FeatureReader; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.referencing.CRS; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.FeatureType; import org.opengis.referencing.crs.CoordinateReferenceSystem; public class KMLFileFormat extends VectorFormat { /** serialVersionUID */ private static final long serialVersionUID = 1L; public static String KML_SRS = "EPSG:4326"; public static CoordinateReferenceSystem KML_CRS; private static KMLPlacemarkTransform kmlTransform = new KMLPlacemarkTransform(); static { try { KML_CRS = CRS.decode(KML_SRS); } catch (Exception e) { throw new RuntimeException("Could not decode: EPSG:4326", e); } } @SuppressWarnings("rawtypes") @Override public FeatureReader read(ImportData data, ImportTask task) throws IOException { File file = getFileFromData(data); // we need to get the feature type, to use for the particular parse through the file // since we put it on the metadata from the list method, we first check if that's still available SimpleFeatureType ft = (SimpleFeatureType) task.getMetadata().get(FeatureType.class); if (ft == null) { // if the type is not available, we can generate one from the resource // we aren't able to ask for the feature type from the resource directly, // because we don't have a backing store FeatureTypeInfo fti = (FeatureTypeInfo) task.getLayer().getResource(); ft = buildFeatureTypeFromInfo(fti); MetadataMap metadata = fti.getMetadata(); if (metadata.containsKey("importschemanames")) { Map<Object, Object> userData = ft.getUserData(); userData.put("schemanames", metadata.get("importschemanames")); } } return read(ft, file); } public FeatureReader<SimpleFeatureType, SimpleFeature> read(SimpleFeatureType featureType, File file) { try { return new KMLTransformingFeatureReader(featureType, new FileInputStream(file)); } catch (Exception e) { throw new RuntimeException(e); } } public FeatureReader<SimpleFeatureType, SimpleFeature> read(SimpleFeatureType featureType, InputStream inputStream) { return new KMLTransformingFeatureReader(featureType, inputStream); } @Override public void dispose(@SuppressWarnings("rawtypes") FeatureReader reader, ImportTask task) throws IOException { reader.close(); } @Override public int getFeatureCount(ImportData data, ImportTask task) throws IOException { // we don't have a fast way to get the count // instead of parsing through the entire file return -1; } @Override public String getName() { return "KML"; } @Override public boolean canRead(ImportData data) throws IOException { File file = getFileFromData(data); return file.canRead() && "kml".equalsIgnoreCase(FilenameUtils.getExtension(file.getName())); } @Override public StoreInfo createStore(ImportData data, WorkspaceInfo workspace, Catalog catalog) throws IOException { // null means no direct store imports can be performed return null; } public Collection<SimpleFeatureType> parseFeatureTypes(String typeName, File file) throws IOException { InputStream inputStream = null; try { inputStream = new FileInputStream(file); return parseFeatureTypes(typeName, inputStream); } finally { if (inputStream != null) { inputStream.close(); } } } private SimpleFeatureType unionFeatureTypes(SimpleFeatureType a, SimpleFeatureType b) { if (a == null) { return b; } SimpleFeatureTypeBuilder ftb = new SimpleFeatureTypeBuilder(); ftb.init(a); List<AttributeDescriptor> attributeDescriptors = a.getAttributeDescriptors(); Set<String> attrNames = new HashSet<String>(attributeDescriptors.size()); for (AttributeDescriptor ad : attributeDescriptors) { attrNames.add(ad.getLocalName()); } for (AttributeDescriptor ad : b.getAttributeDescriptors()) { if (!attrNames.contains(ad.getLocalName())) { ftb.add(ad); } } return ftb.buildFeatureType(); } public SimpleFeatureType convertParsedFeatureType(SimpleFeatureType ft, String name, Set<String> untypedAttributes) { SimpleFeatureType transformedType = kmlTransform.convertFeatureType(ft); SimpleFeatureTypeBuilder ftb = new SimpleFeatureTypeBuilder(); ftb.init(transformedType); Set<String> existringAttrNames = new HashSet<String>(); for (AttributeDescriptor ad : ft.getAttributeDescriptors()) { existringAttrNames.add(ad.getLocalName()); } for (String attr : untypedAttributes) { if (!existringAttrNames.contains(attr)) { ftb.add(attr, String.class); } } ftb.setName(name); ftb.setCRS(KML_CRS); ftb.setSRS(KML_SRS); return ftb.buildFeatureType(); } public List<SimpleFeatureType> parseFeatureTypes(String typeName, InputStream inputStream) throws IOException { KMLRawReader reader = new KMLRawReader(inputStream, KMLRawReader.ReadType.SCHEMA_AND_FEATURES); Set<String> untypedAttributes = new HashSet<String>(); List<String> schemaNames = new ArrayList<String>(); List<SimpleFeatureType> schemas = new ArrayList<SimpleFeatureType>(); SimpleFeatureType aggregateFeatureType = null; for (Object object : reader) { if (object instanceof SimpleFeature) { SimpleFeature feature = (SimpleFeature) object; SimpleFeatureType ft = feature.getFeatureType(); aggregateFeatureType = unionFeatureTypes(aggregateFeatureType, ft); Map<Object, Object> userData = feature.getUserData(); @SuppressWarnings("unchecked") Map<String, Object> untypedData = (Map<String, Object>) userData .get("UntypedExtendedData"); if (untypedData != null) { untypedAttributes.addAll(untypedData.keySet()); } } else if (object instanceof SimpleFeatureType) { SimpleFeatureType schema = (SimpleFeatureType) object; schemas.add(schema); schemaNames.add(schema.getName().getLocalPart()); } } if (aggregateFeatureType == null && schemas.isEmpty()) { throw new IllegalArgumentException("No features found"); } SimpleFeatureType featureType = aggregateFeatureType; for (SimpleFeatureType schema : schemas) { featureType = unionFeatureTypes(featureType, schema); } featureType = convertParsedFeatureType(featureType, typeName, untypedAttributes); if (!schemaNames.isEmpty()) { Map<Object, Object> userData = featureType.getUserData(); userData.put("schemanames", schemaNames); } return Collections.singletonList(featureType); } @Override public List<ImportTask> list(ImportData data, Catalog catalog, ProgressMonitor monitor) throws IOException { File file = getFileFromData(data); CatalogBuilder cb = new CatalogBuilder(catalog); String baseName = typeNameFromFile(file); CatalogFactory factory = catalog.getFactory(); Collection<SimpleFeatureType> featureTypes = parseFeatureTypes(baseName, file); List<ImportTask> result = new ArrayList<ImportTask>(featureTypes.size()); for (SimpleFeatureType featureType : featureTypes) { String name = featureType.getName().getLocalPart(); FeatureTypeInfo ftinfo = factory.createFeatureType(); ftinfo.setEnabled(true); ftinfo.setNativeName(name); ftinfo.setName(name); ftinfo.setTitle(name); ftinfo.setNamespace(catalog.getDefaultNamespace()); List<AttributeTypeInfo> attributes = ftinfo.getAttributes(); for (AttributeDescriptor ad : featureType.getAttributeDescriptors()) { AttributeTypeInfo att = factory.createAttribute(); att.setName(ad.getLocalName()); att.setBinding(ad.getType().getBinding()); attributes.add(att); } LayerInfo layer = cb.buildLayer((ResourceInfo) ftinfo); ResourceInfo resource = layer.getResource(); resource.setSRS(KML_SRS); resource.setNativeCRS(KML_CRS); resource.setNativeBoundingBox(EMPTY_BOUNDS); resource.setLatLonBoundingBox(EMPTY_BOUNDS); resource.getMetadata().put("recalculate-bounds", Boolean.TRUE); Map<Object, Object> userData = featureType.getUserData(); if (userData.containsKey("schemanames")) { MetadataMap metadata = resource.getMetadata(); metadata.put("importschemanames", (Serializable) userData.get("schemanames")); } ImportTask task = new ImportTask(data); task.setLayer(layer); task.getMetadata().put(FeatureType.class, featureType); result.add(task); } return Collections.unmodifiableList(result); } private String typeNameFromFile(File file) { return FilenameUtils.getBaseName(file.getName()); } }