/*
* 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.charts.shoreline;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.metsci.glimpse.util.geo.LatLonGeo;
/**
* Representation of land (shoreline).
*
* Note: LandShape is typically created from a LandFile by calling the toShape() method. This
* shape is often cached using a LandManager class which allows the filename to be specified
* as a parameter and then loads the file to create the LandShape in a static initializer. An
* example is shown below:
* <code>
* public class SampleLandManager <br>
* { <br>
* private static final Params PARAMS = SampleLandManager.getParams(); <br>
* private static final String landFile = PARAMS.getValue(new StringParam("landFile", "")); <br>
* private static final LandShape landShape; <br>
* static <br>
* { <br>
* if (!landFile.isEmpty()) <br>
* { <br>
* landShape = (new NgdcFile(new File(landFilename))).toShape(); <br>
* } <br>
* else <br>
* { <br>
* landShape = null; <br>
* } <br>
* } <br>
* public static LandShape getLandShape() <br>
* { <br>
* return landShape; <br>
* } <br>
* } <br>
* </code>
*/
public class LandShape
{
public static interface VertexConverter
{
void toXY( double lat, double lon, Point2D.Double xy );
}
private final List<LandSegment> segments;
private final LandVertex swCorner;
private final LandVertex neCorner;
private final LandBox box;
private final Shape suShape;
private final VertexConverter suConverter;
private final boolean invertFill;
public LandShape( List<LandSegment> segments, LandBox box )
{
assert segments != null;
this.box = box;
this.segments = Collections.unmodifiableList( new ArrayList<LandSegment>( segments ) );
this.swCorner = new LandVertex( box.southLat, box.westLon );
this.neCorner = new LandVertex( box.northLat, box.eastLon );
this.suConverter = new VertexConverter( )
{
public void toXY( double lat, double lon, Point2D.Double xy )
{
xy.x = ( swCorner.getDistanceX_SU( lon ) );
xy.y = ( swCorner.getDistanceY_SU( lat ) );
}
};
Shape rawSuShape = getRawFillShape( suConverter );
this.invertFill = ( rawSuShape.contains( 0, 0 ) != box.isSwCornerLand );
this.suShape = ( invertFill ? invert( rawSuShape, suConverter ) : rawSuShape );
}
public boolean isLand( double latDeg, double lonDeg )
{
Point2D.Double xy = new Point2D.Double( );
suConverter.toXY( latDeg, lonDeg, xy );
return suShape.contains( xy.getX( ), xy.getY( ) );
}
public Shape getStrokeShape( VertexConverter converter )
{
Path2D stroke = new Path2D.Double( );
Point2D.Double xy = new Point2D.Double( );
for ( LandSegment segment : segments )
{
LandVertex vertex0 = segment.vertices.get( 0 );
converter.toXY( vertex0.lat, vertex0.lon, xy );
stroke.moveTo( xy.getX( ), xy.getY( ) );
for ( int i = 1; i < segment.vertices.size( ); i++ )
{
LandVertex vertex = segment.vertices.get( i );
converter.toXY( vertex.lat, vertex.lon, xy );
stroke.lineTo( xy.getX( ), xy.getY( ) );
}
}
return stroke;
}
public Shape getFillShape( VertexConverter converter )
{
Shape fill = getRawFillShape( converter );
return ( invertFill ? invert( fill, converter ) : fill );
}
private Shape getRawFillShape( VertexConverter converter )
{
Path2D fill = new Path2D.Double( Path2D.WIND_EVEN_ODD );
Point2D.Double xy = new Point2D.Double( );
for ( LandSegment segment : segments )
{
if ( !segment.isFillable ) continue;
LandVertex vertex0 = segment.vertices.get( 0 );
converter.toXY( vertex0.lat, vertex0.lon, xy );
fill.moveTo( xy.getX( ), xy.getY( ) );
for ( int i = 1; i < segment.vertices.size( ); i++ )
{
LandVertex vertex = segment.vertices.get( i );
converter.toXY( vertex.lat, vertex.lon, xy );
fill.lineTo( xy.getX( ), xy.getY( ) );
}
for ( LandVertex ghostVertex : segment.ghostVertices )
{
converter.toXY( ghostVertex.lat, ghostVertex.lon, xy );
fill.lineTo( xy.getX( ), xy.getY( ) );
}
converter.toXY( vertex0.lat, vertex0.lon, xy );
fill.lineTo( xy.getX( ), xy.getY( ) );
}
return fill;
}
private Shape invert( Shape shape, VertexConverter converter )
{
Point2D.Double sw = new Point2D.Double( );
converter.toXY( swCorner.lat, swCorner.lon, sw );
Point2D.Double ne = new Point2D.Double( );
converter.toXY( neCorner.lat, neCorner.lon, ne );
double x = Math.min( sw.getX( ), ne.getX( ) );
double y = Math.min( sw.getY( ), ne.getY( ) );
double w = Math.abs( sw.getX( ) - ne.getX( ) );
double h = Math.abs( sw.getY( ) - ne.getY( ) );
Area inverted = new Area( new Rectangle2D.Double( x, y, w, h ) );
inverted.subtract( new Area( shape ) );
return inverted;
}
public LatLonGeo getSwCorner( )
{
return LatLonGeo.fromDeg( swCorner.lat, swCorner.lon );
}
public LatLonGeo getNeCorner( )
{
return LatLonGeo.fromDeg( neCorner.lat, neCorner.lon );
}
public List<LandSegment> getSegments( )
{
return segments;
}
public LandBox getLandBox( )
{
return box;
}
}