src.gov.nasa.worldwind.layers.LatLonGraticuleLayer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.layers;
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.Logging;
import java.awt.*;
import java.awt.geom.*;
import java.util.ArrayList;
/**
* Displays the geographic latitude/longitude graticule.
*
* @author Patrick Murris
* @version $Id: LatLonGraticuleLayer.java 1690 2013-10-24 19:42:53Z tgaskins $
*/
public class LatLonGraticuleLayer extends AbstractGraticuleLayer
{
public static final String GRATICULE_LATLON_LEVEL_0 = "Graticule.LatLonLevel0";
public static final String GRATICULE_LATLON_LEVEL_1 = "Graticule.LatLonLevel1";
public static final String GRATICULE_LATLON_LEVEL_2 = "Graticule.LatLonLevel2";
public static final String GRATICULE_LATLON_LEVEL_3 = "Graticule.LatLonLevel3";
public static final String GRATICULE_LATLON_LEVEL_4 = "Graticule.LatLonLevel4";
public static final String GRATICULE_LATLON_LEVEL_5 = "Graticule.LatLonLevel5";
protected static final int MIN_CELL_SIZE_PIXELS = 40; // TODO: make settable
protected GraticuleTile[][] gridTiles = new GraticuleTile[18][36]; // 10 degrees row/col
protected ArrayList latitudeLabels = new ArrayList();
protected ArrayList longitudeLabels = new ArrayList();
private String angleFormat = Angle.ANGLE_FORMAT_DMS;
public LatLonGraticuleLayer()
{
initRenderingParams();
this.setPickEnabled(false);
this.setName(Logging.getMessage("layers.LatLonGraticule.Name"));
}
/**
* Get the graticule division and angular display format. Can be one of {@link Angle#ANGLE_FORMAT_DD} or
* {@link Angle#ANGLE_FORMAT_DMS}.
*
* @return the graticule division and angular display format.
*/
public String getAngleFormat()
{
return this.angleFormat;
}
/**
* Sets the graticule division and angular display format. Can be one of {@link Angle#ANGLE_FORMAT_DD},
* {@link Angle#ANGLE_FORMAT_DMS} of {@link Angle#ANGLE_FORMAT_DM}.
*
* @param format the graticule division and angular display format.
*
* @throws IllegalArgumentException is format
is null.
*/
public void setAngleFormat(String format)
{
if (format == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (this.angleFormat.equals(format))
return;
this.angleFormat = format;
this.clearTiles();
this.lastEyePoint = null; // force graticule to update
}
// --- Graticule Rendering --------------------------------------------------------------
protected void initRenderingParams()
{
GraticuleRenderingParams params;
// Ten degrees grid
params = new GraticuleRenderingParams();
params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.WHITE);
params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.WHITE);
params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-16"));
setRenderingParams(GRATICULE_LATLON_LEVEL_0, params);
// One degree
params = new GraticuleRenderingParams();
params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.GREEN);
params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.GREEN);
params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-14"));
setRenderingParams(GRATICULE_LATLON_LEVEL_1, params);
// 1/10th degree - 1/6th (10 minutes)
params = new GraticuleRenderingParams();
params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(0, 102, 255));
params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(0, 102, 255));
setRenderingParams(GRATICULE_LATLON_LEVEL_2, params);
// 1/100th degree - 1/60th (one minutes)
params = new GraticuleRenderingParams();
params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.CYAN);
params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.CYAN);
setRenderingParams(GRATICULE_LATLON_LEVEL_3, params);
// 1/1000 degree - 1/360th (10 seconds)
params = new GraticuleRenderingParams();
params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(0, 153, 153));
params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(0, 153, 153));
setRenderingParams(GRATICULE_LATLON_LEVEL_4, params);
// 1/10000 degree - 1/3600th (one second)
params = new GraticuleRenderingParams();
params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(102, 255, 204));
params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(102, 255, 204));
setRenderingParams(GRATICULE_LATLON_LEVEL_5, params);
}
protected String[] getOrderedTypes()
{
return new String[] {
GRATICULE_LATLON_LEVEL_0,
GRATICULE_LATLON_LEVEL_1,
GRATICULE_LATLON_LEVEL_2,
GRATICULE_LATLON_LEVEL_3,
GRATICULE_LATLON_LEVEL_4,
GRATICULE_LATLON_LEVEL_5,
};
}
protected String getTypeFor(double resolution)
{
if (resolution >= 10)
return GRATICULE_LATLON_LEVEL_0;
else if (resolution >= 1)
return GRATICULE_LATLON_LEVEL_1;
else if (resolution >= .1)
return GRATICULE_LATLON_LEVEL_2;
else if (resolution >= .01)
return GRATICULE_LATLON_LEVEL_3;
else if (resolution >= .001)
return GRATICULE_LATLON_LEVEL_4;
else if (resolution >= .0001)
return GRATICULE_LATLON_LEVEL_5;
return null;
}
protected void clear(DrawContext dc)
{
super.clear(dc);
this.latitudeLabels.clear();
this.longitudeLabels.clear();
this.applyTerrainConformance();
}
private void applyTerrainConformance()
{
String[] graticuleType = getOrderedTypes();
for (String type : graticuleType)
{
getRenderingParams(type).setValue(
GraticuleRenderingParams.KEY_LINE_CONFORMANCE, this.polylineTerrainConformance);
}
}
/**
* Select the visible grid elements
*
* @param dc the current DrawContext
.
*/
protected void selectRenderables(DrawContext dc)
{
ArrayList tileList = getVisibleTiles(dc);
if (tileList.size() > 0)
{
for (GraticuleTile gz : tileList)
{
// Select tile visible elements
gz.selectRenderables(dc);
}
}
}
protected ArrayList getVisibleTiles(DrawContext dc)
{
ArrayList tileList = new ArrayList();
Sector vs = dc.getVisibleSector();
if (vs != null)
{
Rectangle2D gridRectangle = getGridRectangleForSector(vs);
if (gridRectangle != null)
{
for (int row = (int) gridRectangle.getY(); row <= gridRectangle.getY() + gridRectangle.getHeight();
row++)
{
for (int col = (int) gridRectangle.getX(); col <= gridRectangle.getX() + gridRectangle.getWidth();
col++)
{
if (gridTiles[row][col] == null)
gridTiles[row][col] = new GraticuleTile(getGridSector(row, col), 10, 0);
if (gridTiles[row][col].isInView(dc))
tileList.add(gridTiles[row][col]);
else
gridTiles[row][col].clearRenderables();
}
}
}
}
return tileList;
}
private Rectangle2D getGridRectangleForSector(Sector sector)
{
int x1 = getGridColumn(sector.getMinLongitude().degrees);
int x2 = getGridColumn(sector.getMaxLongitude().degrees);
int y1 = getGridRow(sector.getMinLatitude().degrees);
int y2 = getGridRow(sector.getMaxLatitude().degrees);
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
private Sector getGridSector(int row, int col)
{
int minLat = -90 + row * 10;
int maxLat = minLat + 10;
int minLon = -180 + col * 10;
int maxLon = minLon + 10;
return Sector.fromDegrees(minLat, maxLat, minLon, maxLon);
}
private int getGridColumn(Double longitude)
{
int col = (int) Math.floor((longitude + 180) / 10d);
return Math.min(col, 35);
}
private int getGridRow(Double latitude)
{
int row = (int) Math.floor((latitude + 90) / 10d);
return Math.min(row, 17);
}
protected void clearTiles()
{
for (int row = 0; row < 18; row++)
{
for (int col = 0; col < 36; col++)
{
if (this.gridTiles[row][col] != null)
{
this.gridTiles[row][col].clearRenderables();
this.gridTiles[row][col] = null;
}
}
}
}
protected String makeAngleLabel(Angle angle, double resolution)
{
double epsilon = .000000001;
String label;
if (this.getAngleFormat().equals(Angle.ANGLE_FORMAT_DMS))
{
if (resolution >= 1)
label = angle.toDecimalDegreesString(0);
else
{
double[] dms = angle.toDMS();
if (dms[1] < epsilon && dms[2] < epsilon)
label = String.format("%4d\u00B0", (int) dms[0]);
else if (dms[2] < epsilon)
label = String.format("%4d\u00B0 %2d\u2019", (int) dms[0], (int) dms[1]);
else
label = angle.toDMSString();
}
}
else if (this.getAngleFormat().equals(Angle.ANGLE_FORMAT_DM))
{
if (resolution >= 1)
label = angle.toDecimalDegreesString(0);
else
{
double[] dms = angle.toDMS();
if (dms[1] < epsilon && dms[2] < epsilon)
label = String.format("%4d\u00B0", (int) dms[0]);
else if (dms[2] < epsilon)
label = String.format("%4d\u00B0 %2d\u2019", (int) dms[0], (int) dms[1]);
else
label = angle.toDMString();
}
}
else // default to decimal degrees
{
if (resolution >= 1)
label = angle.toDecimalDegreesString(0);
else if (resolution >= .1)
label = angle.toDecimalDegreesString(1);
else if (resolution >= .01)
label = angle.toDecimalDegreesString(2);
else if (resolution >= .001)
label = angle.toDecimalDegreesString(3);
else
label = angle.toDecimalDegreesString(4);
}
return label;
}
protected void addLabel(double value, String labelType, String graticuleType, double resolution, LatLon labelOffset)
{
if (labelType.equals(GridElement.TYPE_LATITUDE_LABEL))
{
if (!this.latitudeLabels.contains(value))
{
this.latitudeLabels.add(value);
String label = makeAngleLabel(Angle.fromDegrees(value), resolution);
GeographicText text = new UserFacingText(label,
Position.fromDegrees(value, labelOffset.getLongitude().degrees, 0));
text.setPriority(resolution * 1e6);
this.addRenderable(text, graticuleType);
}
}
else if (labelType.equals(GridElement.TYPE_LONGITUDE_LABEL))
{
if (!this.longitudeLabels.contains(value))
{
this.longitudeLabels.add(value);
String label = makeAngleLabel(Angle.fromDegrees(value), resolution);
GeographicText text = new UserFacingText(label,
Position.fromDegrees(labelOffset.getLatitude().degrees, value, 0));
text.setPriority(resolution * 1e6);
this.addRenderable(text, graticuleType);
}
}
}
// --- Graticule tile ----------------------------------------------------------------------
protected class GraticuleTile
{
private Sector sector;
private int divisions;
private int level;
private ArrayList gridElements;
private ArrayList subTiles;
public GraticuleTile(Sector sector, int divisions, int level)
{
this.sector = sector;
this.divisions = divisions;
this.level = level;
}
public Extent getExtent(Globe globe, double ve)
{
return Sector.computeBoundingCylinder(globe, ve, this.sector);
}
@SuppressWarnings({"RedundantIfStatement"})
public boolean isInView(DrawContext dc)
{
if (!viewFrustum.intersects(this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration())))
return false;
// Check apparent size
if (this.level != 0 && getSizeInPixels(dc) / this.divisions < MIN_CELL_SIZE_PIXELS)
return false;
return true;
}
public double getSizeInPixels(DrawContext dc)
{
View view = dc.getView();
Vec4 centerPoint = getSurfacePoint(dc, this.sector.getCentroid().getLatitude(),
this.sector.getCentroid().getLongitude());
double distance = view.getEyePoint().distanceTo3(centerPoint);
double tileSizeMeter = this.sector.getDeltaLatRadians() * dc.getGlobe().getRadius();
return tileSizeMeter / view.computePixelSizeAtDistance(distance);
}
public void selectRenderables(DrawContext dc)
{
if (this.gridElements == null)
this.createRenderables();
LatLon labelOffset = computeLabelOffset(dc);
String graticuleType = getTypeFor(this.sector.getDeltaLatDegrees());
if (this.level == 0)
{
for (GridElement ge : this.gridElements)
{
if (ge.isInView(dc))
{
// Add level zero bounding lines and labels
if (ge.type.equals(GridElement.TYPE_LINE_SOUTH) || ge.type.equals(GridElement.TYPE_LINE_WEST))
{
addRenderable(ge.renderable, graticuleType);
String labelType = ge.type.equals(GridElement.TYPE_LINE_SOUTH) ?
GridElement.TYPE_LATITUDE_LABEL : GridElement.TYPE_LONGITUDE_LABEL;
addLabel(ge.value, labelType, graticuleType, this.sector.getDeltaLatDegrees(), labelOffset);
}
}
}
if (getSizeInPixels(dc) / this.divisions < MIN_CELL_SIZE_PIXELS)
return;
}
// Select tile grid elements
double resolution = this.sector.getDeltaLatDegrees() / this.divisions;
graticuleType = getTypeFor(resolution);
for (GridElement ge : this.gridElements)
{
if (ge.isInView(dc))
{
if (ge.type.equals(GridElement.TYPE_LINE))
{
addRenderable(ge.renderable, graticuleType);
String labelType = ge.sector.getDeltaLatDegrees() == 0 ?
GridElement.TYPE_LATITUDE_LABEL : GridElement.TYPE_LONGITUDE_LABEL;
addLabel(ge.value, labelType, graticuleType, resolution, labelOffset);
}
}
}
if (getSizeInPixels(dc) / this.divisions < MIN_CELL_SIZE_PIXELS * 2)
return;
// Select child elements
if (this.subTiles == null)
createSubTiles();
for (GraticuleTile gt : this.subTiles)
{
if (gt.isInView(dc))
{
gt.selectRenderables(dc);
}
else
gt.clearRenderables();
}
}
public void clearRenderables()
{
if (this.gridElements != null)
{
this.gridElements.clear();
this.gridElements = null;
}
if (this.subTiles != null)
{
for (GraticuleTile gt : this.subTiles)
{
gt.clearRenderables();
}
this.subTiles.clear();
this.subTiles = null;
}
}
private void createSubTiles()
{
this.subTiles = new ArrayList();
Sector[] sectors = this.sector.subdivide(this.divisions);
int subDivisions = 10;
if ((getAngleFormat().equals(Angle.ANGLE_FORMAT_DMS) || getAngleFormat().equals(Angle.ANGLE_FORMAT_DM))
&& (this.level == 0 || this.level == 2))
subDivisions = 6;
for (Sector s : sectors)
{
this.subTiles.add(new GraticuleTile(s, subDivisions, this.level + 1));
}
}
/** Create the grid elements */
private void createRenderables()
{
this.gridElements = new ArrayList();
ArrayList positions = new ArrayList();
double step = sector.getDeltaLatDegrees() / this.divisions;
// Generate meridians with labels
double lon = sector.getMinLongitude().degrees + (this.level == 0 ? 0 : step);
while (lon < sector.getMaxLongitude().degrees - step / 2)
{
Angle longitude = Angle.fromDegrees(lon);
// Meridian
positions.clear();
positions.add(new Position(this.sector.getMinLatitude(), longitude, 0));
positions.add(new Position(this.sector.getMaxLatitude(), longitude, 0));
Object polyline = createLineRenderable(positions, Polyline.LINEAR);
Sector sector = Sector.fromDegrees(
this.sector.getMinLatitude().degrees, this.sector.getMaxLatitude().degrees, lon, lon);
String lineType = lon == this.sector.getMinLongitude().degrees ?
GridElement.TYPE_LINE_WEST : GridElement.TYPE_LINE;
GridElement ge = new GridElement(sector, polyline, lineType);
ge.value = lon;
this.gridElements.add(ge);
// Increase longitude
lon += step;
}
// Generate parallels
double lat = this.sector.getMinLatitude().degrees + (this.level == 0 ? 0 : step);
while (lat < this.sector.getMaxLatitude().degrees - step / 2)
{
Angle latitude = Angle.fromDegrees(lat);
positions.clear();
positions.add(new Position(latitude, this.sector.getMinLongitude(), 0));
positions.add(new Position(latitude, this.sector.getMaxLongitude(), 0));
Object polyline = createLineRenderable(positions, Polyline.LINEAR);
Sector sector = Sector.fromDegrees(
lat, lat, this.sector.getMinLongitude().degrees, this.sector.getMaxLongitude().degrees);
String lineType = lat == this.sector.getMinLatitude().degrees ?
GridElement.TYPE_LINE_SOUTH : GridElement.TYPE_LINE;
GridElement ge = new GridElement(sector, polyline, lineType);
ge.value = lat;
this.gridElements.add(ge);
// Increase latitude
lat += step;
}
}
}
}