/** * Copyright (c) Codice Foundation * <p/> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p/> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.catalog.transformer.input.geojson; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.TimeZone; import javax.xml.bind.DatatypeConverter; import org.apache.commons.io.IOUtils; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeType.AttributeFormat; import ddf.catalog.data.Metacard; import ddf.catalog.data.MetacardType; import ddf.catalog.data.MetacardTypeRegistry; import ddf.catalog.data.QualifiedMetacardType; import ddf.catalog.data.impl.MetacardImpl; import ddf.catalog.transform.CatalogTransformerException; import ddf.catalog.transform.InputTransformer; import ddf.geo.formatter.CompositeGeometry; /** * Converts standard GeoJSON (geojson.org) into a Metacard. The limitation on the GeoJSON is that it * must conform to the {@link ddf.catalog.data.impl.BasicTypes#BASIC_METACARD} {@link MetacardType}. * */ public class GeoJsonInputTransformer implements InputTransformer { public static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; protected static final JSONParser PARSER = new JSONParser(); private static final String METACARD_TYPE_PROPERTY_KEY = "metacard-type"; private static final String ID = "geojson"; private static final String MIME_TYPE = "application/json"; private static final String SOURCE_ID_PROPERTY = "source-id"; private static final Logger LOGGER = LoggerFactory.getLogger(GeoJsonInputTransformer.class); private MetacardTypeRegistry mTypeRegistry; public GeoJsonInputTransformer(MetacardTypeRegistry mTypeRegistry) { this.mTypeRegistry = mTypeRegistry; } /** * Transforms GeoJson (http://www.geojson.org/) into a {@link Metacard} */ @Override public Metacard transform(InputStream input) throws IOException, CatalogTransformerException { return transform(input, null); } @Override public Metacard transform(InputStream input, String id) throws IOException, CatalogTransformerException { if (input == null) { throw new CatalogTransformerException("Cannot transform null input."); } JSONObject rootObject = null; try { rootObject = (JSONObject) PARSER.parse(IOUtils.toString(input)); } catch (ParseException e) { LOGGER.error("Parse exception while trying to transform input", e); throw new CatalogTransformerException("Could not parse json text:", e); } Object typeValue = rootObject.get(CompositeGeometry.TYPE_KEY); if (typeValue == null || !typeValue.equals("Feature")) { throw new CatalogTransformerException(new UnsupportedOperationException( "Only supported type is Feature, not [" + typeValue + "]")); } JSONObject properties = (JSONObject) rootObject.get(CompositeGeometry.PROPERTIES_KEY); if (properties == null) { throw new CatalogTransformerException("Properties are required to create a Metacard."); } String metacardTypeName = (String) properties.get(METACARD_TYPE_PROPERTY_KEY); MetacardImpl metacard = null; if (metacardTypeName == null || metacardTypeName.isEmpty() || mTypeRegistry == null) { LOGGER.debug( "MetacardType specified in input is null or empty. Assuming default MetacardType"); metacard = new MetacardImpl(); } else { QualifiedMetacardType metacardType = mTypeRegistry.lookup(metacardTypeName); if (metacardType == null) { String message = "MetacardType specified in input has not been registered with the system. Cannot parse input. MetacardType name: " + metacardTypeName; LOGGER.warn(message); throw new CatalogTransformerException(message); } LOGGER.debug("Found registered MetacardType: {}", metacardTypeName); metacard = new MetacardImpl(metacardType); } MetacardType metacardType = metacard.getMetacardType(); metacardTypeName = metacardType.getName(); LOGGER.debug("Metacard type name: {}", metacardType.getName()); // retrieve geometry JSONObject geometryJson = (JSONObject) rootObject.get(CompositeGeometry.GEOMETRY_KEY); CompositeGeometry geoJsonGeometry = null; if (geometryJson != null) { if (geometryJson.get(CompositeGeometry.TYPE_KEY) != null && geometryJson.get(CompositeGeometry.COORDINATES_KEY) != null) { String geometryTypeJson = geometryJson.get(CompositeGeometry.TYPE_KEY).toString(); geoJsonGeometry = CompositeGeometry .getCompositeGeometry(geometryTypeJson, geometryJson); } else { LOGGER.warn("Could not find geometry type."); } } // find where the geometry goes String geoAttributeName = null; for (AttributeDescriptor ad : metacardType.getAttributeDescriptors()) { if (AttributeFormat.GEOMETRY.equals(ad.getType().getAttributeFormat())) { geoAttributeName = ad.getName(); } } if (geoJsonGeometry != null) { if (geoAttributeName != null) { metacard.setAttribute(geoAttributeName, geoJsonGeometry.toWkt()); } else { LOGGER.warn("Loss of data, could not place geometry [{}] in metacard", geoJsonGeometry.toWkt()); } } // TODO read which metatype they need, find the metatype and use it for // reading the data format for (AttributeDescriptor ad : metacardType.getAttributeDescriptors()) { try { if (properties.containsKey(ad.getName())) { Object attributeValue = properties.get(ad.getName()); if (attributeValue != null) { String attributeString = attributeValue.toString(); switch (ad.getType().getAttributeFormat()) { case BINARY: metacard.setAttribute(ad.getName(), DatatypeConverter.parseBase64Binary(attributeString)); break; case DATE: try { SimpleDateFormat dateFormat = new SimpleDateFormat( ISO_8601_DATE_FORMAT); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); metacard.setAttribute(ad.getName(), dateFormat.parse(attributeString)); } catch (java.text.ParseException e) { throw new CatalogTransformerException("Could not parse Date:", e); } break; case GEOMETRY: break; case STRING: case XML: metacard.setAttribute(ad.getName(), attributeString); break; case BOOLEAN: metacard.setAttribute(ad.getName(), Boolean.parseBoolean(attributeString)); break; case SHORT: metacard.setAttribute(ad.getName(), Short.parseShort(attributeString)); break; case INTEGER: metacard.setAttribute(ad.getName(), Integer.parseInt(attributeString)); break; case LONG: metacard.setAttribute(ad.getName(), Long.parseLong(attributeString)); break; case FLOAT: metacard.setAttribute(ad.getName(), Float.parseFloat(attributeString)); break; case DOUBLE: metacard.setAttribute(ad.getName(), Double.parseDouble(attributeString)); break; default: break; } } } } catch (NumberFormatException e) { LOGGER.info( "GeoJSON input for attribute name '{}' does not match expected AttributeType defined in MetacardType: {}. This attribute will not be added to the metacard.", ad.getName(), metacardTypeName, e); } } if (metacard.getSourceId() != null && !"".equals(metacard.getSourceId())) { properties.put(SOURCE_ID_PROPERTY, metacard.getSourceId()); } if (id != null) { metacard.setId(id); } return metacard; } @Override public String toString() { return "InputTransformer {Impl=" + this.getClass().getName() + ", id=" + ID + ", mime-type=" + MIME_TYPE + "}"; } }