package mil.nga.giat.geowave.format.gpx; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.geotools.feature.AttributeTypeBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.opengis.feature.simple.SimpleFeatureType; import org.xml.sax.SAXException; import com.vividsolutions.jts.geom.Geometry; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * This is a convenience class for performing common GPX static utility methods * such as schema validation, file parsing, and SimpleFeatureType definition. */ public class GpxUtils { private final static Logger LOGGER = LoggerFactory.getLogger(GpxUtils.class); private static final String SCHEMA_RESOURCE_PACKAGE = "mil/nga/giat/geowave/types/gpx/"; private static final String SCHEMA_GPX_1_0_LOCATION = SCHEMA_RESOURCE_PACKAGE + "gpx-1_0.xsd"; private static final String SCHEMA_GPX_1_1_LOCATION = SCHEMA_RESOURCE_PACKAGE + "gpx-1_1.xsd"; private static final URL SCHEMA_GPX_1_0_URL = GpxUtils.class.getClassLoader().getResource( SCHEMA_GPX_1_0_LOCATION); private static final URL SCHEMA_GPX_1_1_URL = GpxUtils.class.getClassLoader().getResource( SCHEMA_GPX_1_1_LOCATION); private static final Validator SCHEMA_GPX_1_0_VALIDATOR = getSchemaValidator(SCHEMA_GPX_1_0_URL); private static final Validator SCHEMA_GPX_1_1_VALIDATOR = getSchemaValidator(SCHEMA_GPX_1_1_URL); public static final String GPX_POINT_FEATURE = "gpxpoint"; public static final String GPX_ROUTE_FEATURE = "gpxroute"; public static final String GPX_TRACK_FEATURE = "gpxtrack"; public static final String GPX_WAYPOINT_FEATURE = "gpxwaypoint"; private static final ThreadLocal<DateFormat> dateFormatSeconds = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'"); } }; private static final ThreadLocal<DateFormat> dateFormatMillis = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } }; public static Date parseDateSeconds( final String source ) throws ParseException { return dateFormatSeconds.get().parse( source); } public static Date parseDateMillis( final String source ) throws ParseException { return dateFormatMillis.get().parse( source); } @SuppressFBWarnings({ "SF_SWITCH_NO_DEFAULT" }) public static Map<Long, GpxTrack> parseOsmMetadata( final File metadataFile ) throws FileNotFoundException, XMLStreamException { final Map<Long, GpxTrack> metadata = new HashMap<Long, GpxTrack>(); final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); XMLEventReader eventReader = null; try (final FileInputStream fis = new FileInputStream( metadataFile); final InputStream in = new BufferedInputStream( fis);) { inputFactory.setProperty( "javax.xml.stream.isSupportingExternalEntities", false); eventReader = inputFactory.createXMLEventReader(in); while (eventReader.hasNext()) { XMLEvent event = eventReader.nextEvent(); if (event.isStartElement()) { StartElement node = event.asStartElement(); switch (node.getName().getLocalPart()) { case "gpxFile": { final GpxTrack gt = new GpxTrack(); node = event.asStartElement(); @SuppressWarnings("unchecked") final Iterator<Attribute> attributes = node.getAttributes(); while (attributes.hasNext()) { final Attribute a = attributes.next(); switch (a.getName().getLocalPart()) { case "id": { gt.setTrackid(Long.parseLong(a.getValue())); break; } case "timestamp": { try { gt.setTimestamp(parseDateSeconds( a.getValue()).getTime()); } catch (final Exception t) { try { gt.setTimestamp(parseDateMillis( a.getValue()).getTime()); } catch (final Exception t2) { LOGGER.warn( "Unable to format time: " + a.getValue(), t2); } } break; } case "points": { gt.setPoints(Long.parseLong(a.getValue())); break; } case "visibility": { gt.setVisibility(a.getValue()); break; } case "uid": { gt.setUserid(Long.parseLong(a.getValue())); break; } case "user": { gt.setUser(a.getValue()); break; } } } while (!(event.isEndElement() && event.asEndElement().getName().getLocalPart().equals( "gpxFile"))) { if (event.isStartElement()) { node = event.asStartElement(); switch (node.getName().getLocalPart()) { case "description": { event = eventReader.nextEvent(); if (event.isCharacters()) { gt.setDescription(event.asCharacters().getData()); } break; } case "tags": { final List<String> tags = new ArrayList<String>(); while (!(event.isEndElement() && event .asEndElement() .getName() .getLocalPart() .equals( "tags"))) { if (event.isStartElement()) { node = event.asStartElement(); if (node.getName().getLocalPart().equals( "tag")) { event = eventReader.nextEvent(); if (event.isCharacters()) { tags.add(event.asCharacters().getData()); } } } event = eventReader.nextEvent(); } gt.setTags(tags); break; } } } event = eventReader.nextEvent(); } metadata.put( gt.getTrackid(), gt); break; } } } } } catch (IOException e) { LOGGER.error( "Could not create the FileInputStream.", e); } return metadata; } public static SimpleFeatureType createGPXTrackDataType() { final SimpleFeatureTypeBuilder simpleFeatureTypeBuilder = new SimpleFeatureTypeBuilder(); simpleFeatureTypeBuilder.setName(GPX_TRACK_FEATURE); final AttributeTypeBuilder attributeTypeBuilder = new AttributeTypeBuilder(); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Geometry.class).nillable( true).buildDescriptor( "geometry")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Name")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Date.class).nillable( true).buildDescriptor( "StartTimeStamp")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Date.class).nillable( true).buildDescriptor( "EndTimeStamp")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Long.class).nillable( true).buildDescriptor( "Duration")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Long.class).nillable( true).buildDescriptor( "NumberPoints")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "TrackId")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Long.class).nillable( true).buildDescriptor( "UserId")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "User")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Description")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Tags")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Source")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Comment")); return simpleFeatureTypeBuilder.buildFeatureType(); } public static SimpleFeatureType createGPXRouteDataType() { final SimpleFeatureTypeBuilder simpleFeatureTypeBuilder = new SimpleFeatureTypeBuilder(); simpleFeatureTypeBuilder.setName(GPX_ROUTE_FEATURE); final AttributeTypeBuilder attributeTypeBuilder = new AttributeTypeBuilder(); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Geometry.class).nillable( true).buildDescriptor( "geometry")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Name")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Long.class).nillable( true).buildDescriptor( "NumberPoints")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "TrackId")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Symbol")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "User")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Description")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Source")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Comment")); return simpleFeatureTypeBuilder.buildFeatureType(); } public static SimpleFeatureType createGPXPointDataType() { final SimpleFeatureTypeBuilder simpleFeatureTypeBuilder = new SimpleFeatureTypeBuilder(); simpleFeatureTypeBuilder.setName(GPX_POINT_FEATURE); final AttributeTypeBuilder attributeTypeBuilder = new AttributeTypeBuilder(); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Geometry.class).nillable( true).buildDescriptor( "geometry")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Latitude")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Longitude")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Elevation")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Date.class).nillable( true).buildDescriptor( "Timestamp")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Comment")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Integer.class).nillable( true).buildDescriptor( "Satellites")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "VDOP")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "HDOP")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "PDOP")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Symbol")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Classification")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "GeoHeight")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Course")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "MagneticVariation")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Source")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Link")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Fix")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Integer.class).nillable( true).buildDescriptor( "Station")); return simpleFeatureTypeBuilder.buildFeatureType(); } public static SimpleFeatureType createGPXWaypointDataType() { final SimpleFeatureTypeBuilder simpleFeatureTypeBuilder = new SimpleFeatureTypeBuilder(); simpleFeatureTypeBuilder.setName(GPX_WAYPOINT_FEATURE); final AttributeTypeBuilder attributeTypeBuilder = new AttributeTypeBuilder(); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Geometry.class).nillable( true).buildDescriptor( "geometry")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Latitude")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Longitude")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Elevation")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Name")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Comment")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Description")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Symbol")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Link")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Source")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Integer.class).nillable( true).buildDescriptor( "Station")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "URL")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "URLName")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( String.class).nillable( true).buildDescriptor( "Fix")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "MagneticVariation")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "GeoHeight")); simpleFeatureTypeBuilder.add(attributeTypeBuilder.binding( Double.class).nillable( true).buildDescriptor( "Elevation")); return simpleFeatureTypeBuilder.buildFeatureType(); } private static Validator getSchemaValidator( final URL schemaUrl ) { final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema; try { schema = schemaFactory.newSchema(schemaUrl); return schema.newValidator(); } catch (final SAXException e) { LOGGER.warn( "Unable to read schema '" + schemaUrl.toString() + "'", e); } return null; } public static boolean validateGpx( final File gpxDocument ) throws SAXException, IOException { final Source xmlFile = new StreamSource( gpxDocument); try { SCHEMA_GPX_1_1_VALIDATOR.validate(xmlFile); return true; } catch (final SAXException e) { LOGGER.info( "XML file '" + "' failed GPX 1.1 validation", e); try { SCHEMA_GPX_1_0_VALIDATOR.validate(xmlFile); return true; } catch (final SAXException e2) { LOGGER.info( "XML file '" + "' failed GPX 1.0 validation", e2); } return false; } } }