/* * Copyright (c) 2016, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Metron, Inc. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.metsci.glimpse.examples.dspl; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import com.metsci.glimpse.axis.Axis1D; import com.metsci.glimpse.axis.Axis2D; import com.metsci.glimpse.axis.listener.mouse.AxisMouseListener2D; import com.metsci.glimpse.axis.painter.label.AxisUnitConverter; import com.metsci.glimpse.axis.tagged.Tag; import com.metsci.glimpse.dspl.DsplParser; import com.metsci.glimpse.dspl.parser.column.TableColumn; import com.metsci.glimpse.dspl.parser.table.SliceTableData; import com.metsci.glimpse.dspl.schema.Concept; import com.metsci.glimpse.dspl.schema.DataSet; import com.metsci.glimpse.dspl.schema.DataType; import com.metsci.glimpse.dspl.schema.Slice; import com.metsci.glimpse.dspl.util.DsplSliceUtils.ConceptPattern; import com.metsci.glimpse.dspl.util.DsplSliceUtils.SimpleConceptPattern; import com.metsci.glimpse.dspl.util.DsplSliceUtils.SimpleSlicePattern; import com.metsci.glimpse.event.mouse.GlimpseMouseEvent; import com.metsci.glimpse.event.mouse.GlimpseMouseMotionListener; import com.metsci.glimpse.examples.Example; import com.metsci.glimpse.layout.GlimpseAxisLayout2D; import com.metsci.glimpse.layout.GlimpseLayout; import com.metsci.glimpse.layout.GlimpseLayoutProvider; import com.metsci.glimpse.painter.decoration.BackgroundPainter; import com.metsci.glimpse.painter.decoration.GridPainter; import com.metsci.glimpse.painter.geo.ScalePainter; import com.metsci.glimpse.painter.track.TrackPainter; import com.metsci.glimpse.plot.timeline.StackedTimePlot2D; import com.metsci.glimpse.plot.timeline.data.Epoch; import com.metsci.glimpse.plot.timeline.layout.TimePlotInfo; import com.metsci.glimpse.support.color.GlimpseColor; import com.metsci.glimpse.util.Pair; import com.metsci.glimpse.util.geo.LatLonGeo; import com.metsci.glimpse.util.geo.projection.GeoProjection; import com.metsci.glimpse.util.geo.projection.TangentPlane; import com.metsci.glimpse.util.units.Length; import com.metsci.glimpse.util.units.time.Time; import com.metsci.glimpse.util.units.time.TimeStamp; import com.metsci.glimpse.util.vector.Vector2d; public class DsplTrackExample implements GlimpseLayoutProvider { public static final Logger logger = Logger.getLogger( DsplExample.class.getName( ) ); public static void main( String[] args ) throws Exception { Example.showWithSwing( new DsplTrackExample( ) ); } @Override public GlimpseLayout getLayout( ) throws Exception { // create a painter for displaying track data final TrackPainter trackPainter = new TrackPainter( ); // TrackPainter requires tracks have integer ids, so we need to keep a // map from the dspl track ids to the integer ids we assign Map<String, Integer> trackIdMap = new HashMap<String, Integer>( ); // create a counter to use to assign track ids int nextTrackId = 0; // used to calculate the bounds of the data double minX = Double.POSITIVE_INFINITY; double minY = Double.POSITIVE_INFINITY; double maxX = Double.NEGATIVE_INFINITY; double maxY = Double.NEGATIVE_INFINITY; long minTime = Long.MAX_VALUE; long maxTime = Long.MIN_VALUE; // create a dspl parser DsplParser parser = new DsplParser( ); // tell the parser not to grab canonical concept files from the web parser.setNetworkMode( false ); // tell the parser not to create cached copies of the csv data files it loads // if true, loaded dspl files will be converted behind the scenes to an efficient // binary file format and will load much faster the second time they are loaded parser.setCacheMode( false ); // load the example dataset DataSet dataset = parser.loadDataset( "src/main/resources/dspl/track/metadata.xml" ); // define patterns which will help us look for particular Concepts in the dataset ConceptPattern identifierPattern = new SimpleConceptPattern( "http://www.metsci.com/dspl/track", "identifier" ); ConceptPattern timePattern = new SimpleConceptPattern( "http://www.metsci.com/dspl/time", "millisecond" ); List<ConceptPattern> dimensionPatterns = new ArrayList<ConceptPattern>( ); dimensionPatterns.add( identifierPattern ); dimensionPatterns.add( timePattern ); ConceptPattern latPattern = new SimpleConceptPattern( "http://www.metsci.com/dspl/track", "latitude_degrees" ); ConceptPattern lonPattern = new SimpleConceptPattern( "http://www.metsci.com/dspl/track", "longitude_degrees" ); List<ConceptPattern> metricPatterns = new ArrayList<ConceptPattern>( ); metricPatterns.add( latPattern ); metricPatterns.add( lonPattern ); SimpleSlicePattern slicePattern = new SimpleSlicePattern( dimensionPatterns, metricPatterns ); // a projection for converting between planar x/y and lat/lon coordinates GeoProjection projection = null; // iterate through the Slices found in the DSPL Dataset which contain // the data concepts that we know how to handle // this is a polymorphic operation, so we will find Slices which contain // sub-concepts of our concepts of interest as well for ( Slice slice : slicePattern.find( dataset ) ) { SliceTableData data = slice.getTableData( ); // retrieve Concepts describing the data contained in the DSPL Slice Concept identifierConcept = identifierPattern.findDimension( slice ); Concept timeConcept = timePattern.findDimension( slice ); Concept latConcept = latPattern.findMetric( slice ); Concept lonConcept = lonPattern.findMetric( slice ); // find data columns using associated with the Slice Concepts TableColumn identifierColumn = data.getDimensionColumn( identifierConcept ); TableColumn timeColumn = data.getDimensionColumn( timeConcept ); TableColumn latColumn = data.getMetricColumn( latConcept ); TableColumn lonColumn = data.getMetricColumn( lonConcept ); // iterate through the data rows in the dspl data file for ( int i = 0; i < data.getNumRows( ); i++ ) { // track identifier String identifier = identifierColumn.getStringData( i ); // absolute timestamp in posix milliseconds (since epoch) long time = timeColumn.getDateData( i ); // latitude and longitude of track point float lat_deg = latColumn.getFloatData( i ); float lon_deg = lonColumn.getFloatData( i ); // project track point lat/lon onto a TangentPlane LatLonGeo latlon = LatLonGeo.fromDeg( lat_deg, lon_deg ); if ( projection == null ) { projection = new TangentPlane( latlon ); } Vector2d point = projection.project( latlon ); // update the bounding box for the data if ( minX > point.getX( ) ) minX = point.getX( ); if ( minY > point.getY( ) ) minY = point.getY( ); if ( maxX < point.getX( ) ) maxX = point.getX( ); if ( maxY < point.getY( ) ) maxY = point.getY( ); if ( minTime > time ) minTime = time; if ( maxTime < time ) maxTime = time; // retrieve the integer track id associated with the string identifier // or assign one if none exists yet Integer trackId = trackIdMap.get( identifier ); if ( trackId == null ) { trackId = nextTrackId++; trackIdMap.put( identifier, trackId ); initializeGeoTrack( trackPainter, trackId ); } // add the point from the dspl data set to the track painter trackPainter.addPoint( trackId, 0, point.getX( ), point.getY( ), time ); } } // create an Axis2D to define the bounds of the plotting area Axis2D axis = new Axis2D( ); // set the axis bounds (display all the data initially) axis.set( minX, maxX, minY, maxY ); axis.lockAspectRatioXY( 1.0 ); // set time bounds (display all the data initially) trackPainter.displayTimeRange( minTime, maxTime ); // create a layout to draw the geographic data GlimpseAxisLayout2D geoPlot = new GlimpseAxisLayout2D( "DsplTrackExampleGeo", axis ); // add an axis mouse listener to the layout so that it responds to user pans/zooms geoPlot.addGlimpseMouseAllListener( new AxisMouseListener2D( ) ); // add a solid color background painter to the plot geoPlot.addPainter( new BackgroundPainter( ).setColor( GlimpseColor.getBlack( ) ) ); // add grid lines to the plot geoPlot.addPainter( new GridPainter( ).setLineColor( GlimpseColor.getGray( 0.7f ) ).setShowMinorGrid( true ) ); // add the track painter with dspl data loaded into it into the plot geoPlot.addPainter( trackPainter ); // add a scale indicator to the plot ScalePainter scale = new ScalePainter( ); scale.setPixelBufferX( 8 ); scale.setPixelBufferY( 8 ); scale.setUnitLabel( "ft" ); scale.setUnitConverter( new AxisUnitConverter( ) { @Override public double fromAxisUnits( double value ) { return Length.fromFeet( value ); } @Override public double toAxisUnits( double value ) { return Length.toFeet( value ); } } ); geoPlot.addPainter( scale ); // convert the minimum and maximum timestamps from the data set to TimeStamps TimeStamp minTimestamp = TimeStamp.fromPosixMillis( minTime ); TimeStamp maxTimestamp = TimeStamp.fromPosixMillis( maxTime ); // create an Epoch defining what absolute timestamp corresponds to 0 on the timeline // here we use the minimum timestamp from the data set // the Epoch should always be chosen to be close to the data set times final Epoch epoch = new Epoch( TimeStamp.fromPosixMillis( minTime ) ); // create a timeline plot final StackedTimePlot2D timePlot = new StackedTimePlot2D( epoch ); // customize the coloring on the timeline timePlot.setBackgroundColor( GlimpseColor.fromColorRgb( 25, 42, 62 ) ); timePlot.getDefaultTimeline( ).setAxisColor( GlimpseColor.getWhite( ) ); // set the selected time range on the timeline timePlot.setTimeSelection( minTimestamp, maxTimestamp ); // set the bounds of the timeline to slightly beyond the selected time range timePlot.setTimeAxisBounds( minTimestamp.subtract( Time.fromHours( 1 ) ), maxTimestamp.add( Time.fromHours( 1 ) ) ); // add a mouse listener which updates the visible section of the geographic track // based on the selected time region timePlot.getOverlayLayout( ).addGlimpseMouseMotionListener( new GlimpseMouseMotionListener( ) { @Override public void mouseMoved( GlimpseMouseEvent e ) { Tag minTag = timePlot.getTimeSelectionMinTag( ); Tag maxTag = timePlot.getTimeSelectionMaxTag( ); TimeStamp selectionMin = epoch.toTimeStamp( minTag.getValue( ) ); TimeStamp selectionMax = epoch.toTimeStamp( maxTag.getValue( ) ); trackPainter.displayTimeRange( selectionMin.toPosixMillis( ), selectionMax.toPosixMillis( ) ); } } ); // create a parent plot to contain the geographic and time plots GlimpseLayout parentLayout = new GlimpseLayout( "DsplTrackExampleParent" ); // add the geographic and time plots to the parent parentLayout.addLayout( geoPlot ); parentLayout.addLayout( timePlot ); // set layout constraints to position the geographic and time plots within the parent layout geoPlot.setLayoutData( "cell 0 0 1 1, push, grow" ); timePlot.setLayoutData( "cell 0 1 1 1, pushx, growx, height 200!" ); // iterate through the dataset again, this time looking for other // data series which we can display on the timeline // should really use Concept as key, but Concept doesn't have a good hash code at the moment Map<String, Pair<TrackPainter, TimePlotInfo>> plotMap = new HashMap<String, Pair<TrackPainter, TimePlotInfo>>( ); for ( Slice slice : slicePattern.find( dataset ) ) { SliceTableData data = slice.getTableData( ); // we still need the track identifier and time columns Concept identifierConcept = identifierPattern.findDimension( slice ); Concept timeConcept = timePattern.findDimension( slice ); TableColumn identifierColumn = data.getDimensionColumn( identifierConcept ); TableColumn timeColumn = data.getDimensionColumn( timeConcept ); // iterate through all the other metric columns in the data set for ( String id : data.getMetricColumnIds( ) ) { // get the column and concept TableColumn column = data.getMetricColumn( id ); Concept concept = column.getConcept( ); // don't display latitude or longitude on timeline plots if ( concept == null || latPattern.matches( concept ) || lonPattern.matches( concept ) ) continue; DataType type = concept.getType( ).getRef( ); // we only display FLOAT and INTEGER type data on the lineplot if ( type != DataType.FLOAT && type == DataType.INTEGER ) continue; // we will create a timeline plot for each additional concept in the data set Pair<TrackPainter, TimePlotInfo> pair = plotMap.get( id ); if ( pair == null ) { TimePlotInfo plotInfo = timePlot.createTimePlot( id ); TrackPainter plotPainter = new TrackPainter( ); for ( Integer trackId : trackIdMap.values( ) ) { initializeTimelinePlot( plotPainter, trackId ); } plotInfo.addPainter( plotPainter ); initializePlot( concept.getNameEnglish( ), plotInfo ); pair = new Pair<TrackPainter, TimePlotInfo>( plotPainter, plotInfo ); plotMap.put( id, pair ); } TrackPainter plotPainter = pair.first( ); TimePlotInfo plotInfo = pair.second( ); minY = Double.POSITIVE_INFINITY; maxY = Double.NEGATIVE_INFINITY; // iterate through the data set rows for ( int i = 0; i < data.getNumRows( ); i++ ) { String identifier = identifierColumn.getStringData( i ); Integer trackId = trackIdMap.get( identifier ); double dataY = 0.0; if ( type == DataType.INTEGER ) { dataY = column.getIntegerData( i ); } else if ( type == DataType.FLOAT ) { dataY = column.getFloatData( i ); } if ( minY > dataY ) minY = dataY; if ( maxY < dataY ) maxY = dataY; long time = timeColumn.getDateData( i ); TimeStamp timestamp = TimeStamp.fromPosixMillis( time ); double timeX = epoch.fromTimeStamp( timestamp ); plotPainter.addPoint( trackId, 0, timeX, dataY, time ); } Axis1D axisY = plotInfo.getOrthogonalAxis( ); axisY.setMin( minY ); axisY.setMax( maxY ); } } // if there were no additional data columns, create an empty timeline plot anyway if ( plotMap.isEmpty( ) ) { TimePlotInfo plot = timePlot.createTimePlot( "default" ); initializePlot( "default", plot ); } return parentLayout; } protected void initializePlot( String name, TimePlotInfo plot ) { plot.setLabelText( name ); plot.setAxisColor( GlimpseColor.getWhite( ) ); plot.setLabelColor( GlimpseColor.getWhite( ) ); } // setup the visual characteristics (color, thickness, etc...) of the track protected void initializeGeoTrack( TrackPainter trackPainter, int trackId ) { trackPainter.setLineColor( trackId, GlimpseColor.getBlue( ) ); trackPainter.setLineWidth( trackId, 2.0f ); trackPainter.setShowLines( trackId, true ); trackPainter.setShowPoints( trackId, false ); trackPainter.setShowHeadPoint( trackId, false ); } protected void initializeTimelinePlot( TrackPainter trackPainter, int trackId ) { trackPainter.setLineColor( trackId, GlimpseColor.getRed( ) ); trackPainter.setLineWidth( trackId, 2.0f ); trackPainter.setShowLines( trackId, true ); trackPainter.setShowPoints( trackId, false ); trackPainter.setShowHeadPoint( trackId, false ); } }