/** * 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.metacard.geojson; import java.io.ByteArrayInputStream; import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.activation.MimeType; import javax.activation.MimeTypeParseException; import javax.xml.bind.DatatypeConverter; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import ddf.catalog.data.Attribute; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeType; import ddf.catalog.data.BinaryContent; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.BinaryContentImpl; import ddf.catalog.transform.CatalogTransformerException; import ddf.catalog.transform.MetacardTransformer; import ddf.geo.formatter.CompositeGeometry; /** * Implements the {@link MetacardTransformer} interface to transform a single {@link Metacard} * instance to GeoJSON. This class places what is returned by {@link Metacard#getLocation()} in the * geometry JSON object in the GeoJSON output. The rest of the attributes of the Metacard are placed * in the properties object in the JSON. See geojson.org for the GeoJSON specification. * * @author Ashraf Barakat * @author ddf.isgs@lmco.com * * @see MetacardTransformer * @see Metacard * @see Attribute * */ public class GeoJsonMetacardTransformer implements MetacardTransformer { public static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; public static final String ID = "geojson"; protected static final String METACARD_TYPE_PROPERTY_KEY = "metacard-type"; private static final Logger LOGGER = LoggerFactory.getLogger(GeoJsonMetacardTransformer.class); private static final String SOURCE_ID_PROPERTY = "source-id"; protected static final MimeType DEFAULT_MIME_TYPE = new MimeType(); static { try { DEFAULT_MIME_TYPE.setPrimaryType("application"); DEFAULT_MIME_TYPE.setSubType("json"); } catch (MimeTypeParseException e) { LOGGER.info("Failure creating MIME type", e); throw new ExceptionInInitializerError(e); } } public static JSONObject convertToJSON(Metacard metacard) throws CatalogTransformerException { if (metacard == null) { throw new CatalogTransformerException("Cannot transform null metacard."); } // must be LinkedHashMap to maintain order JSONObject rootObject = new JSONObject(); rootObject.put("type", "Feature"); JSONObject properties = new JSONObject(); for (AttributeDescriptor ad : metacard.getMetacardType() .getAttributeDescriptors()) { Attribute attribute = metacard.getAttribute(ad.getName()); if (attribute != null) { Object value = convertAttribute(attribute, ad); if (value != null) { if (Metacard.GEOGRAPHY.equals(attribute.getName())) { rootObject.put(CompositeGeometry.GEOMETRY_KEY, value); } else { properties.put(attribute.getName(), value); } } } } if (rootObject.get(CompositeGeometry.GEOMETRY_KEY) == null) { rootObject.put(CompositeGeometry.GEOMETRY_KEY, null); } properties.put(METACARD_TYPE_PROPERTY_KEY, metacard.getMetacardType() .getName()); if (metacard.getSourceId() != null && !"".equals(metacard.getSourceId())) { properties.put(SOURCE_ID_PROPERTY, metacard.getSourceId()); } rootObject.put(CompositeGeometry.PROPERTIES_KEY, properties); return rootObject; } @Override public BinaryContent transform(Metacard metacard, Map<String, Serializable> arguments) throws CatalogTransformerException { JSONObject rootObject = convertToJSON(metacard); String jsonText = JSONValue.toJSONString(rootObject); return new BinaryContentImpl(new ByteArrayInputStream(jsonText.getBytes(StandardCharsets.UTF_8)), DEFAULT_MIME_TYPE); } @Override public String toString() { return MetacardTransformer.class.getName() + " {Impl=" + this.getClass() .getName() + ", id=" + ID + ", MIME Type=" + DEFAULT_MIME_TYPE + "}"; } private static Object convertAttribute(Attribute attribute, AttributeDescriptor descriptor) throws CatalogTransformerException { if (descriptor.isMultiValued()) { List<Object> values = new ArrayList<>(); for (Serializable value : attribute.getValues()) { values.add(convertValue(value, descriptor.getType() .getAttributeFormat())); } return values; } else { return convertValue(attribute.getValue(), descriptor.getType() .getAttributeFormat()); } } private static Object convertValue(Serializable value, AttributeType.AttributeFormat format) throws CatalogTransformerException { if (value == null) { return null; } switch (format) { case BOOLEAN: return value; case DATE: SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_DATE_FORMAT); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); return dateFormat.format((Date) value); case BINARY: byte[] bytes = (byte[]) value; String base64 = DatatypeConverter.printBase64Binary(bytes); return base64; case DOUBLE: case LONG: case FLOAT: case INTEGER: case SHORT: case STRING: case XML: return value.toString(); case GEOMETRY: WKTReader reader = new WKTReader(); try { Geometry geometry = reader.read(value.toString()); CompositeGeometry geoJsonGeometry = CompositeGeometry.getCompositeGeometry(geometry); if (geoJsonGeometry == null) { throw new CatalogTransformerException( "Could not perform transform: unsupported geometry [" + value + "]"); } return geoJsonGeometry.toJsonMap(); } catch (ParseException e) { throw new CatalogTransformerException( "Could not perform transform: could not parse geometry [" + value + "]", e); } case OBJECT: default: return null; } } }