All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.util.measure.LengthMeasurer Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * 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.util.measure;

import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.render.Polyline;

import java.util.ArrayList;

/**
 * Utility class to measure length along a path on a globe.
 *
 * 

The measurer must be provided a list of at least two positions to be able to compute a distance.

* *

Segments which are longer then the current maxSegmentLength will be subdivided along lines following the current * pathType - Polyline.LINEAR, Polyline.RHUMB_LINE or Polyline.GREAT_CIRCLE.

* *

If the measurer is set to follow terrain, the computed length will account for terrain deformations as * if someone was walking along that path. Otherwise the length is the sum of the cartesian distance between * each positions.

* *

When following terrain the measurer will sample terrain elevations at regular intervals along the path. The * minimum number of samples used for the whole length can be set with setLengthTerrainSamplingSteps(). However, * the sampling process will keep a minimum interval of 30 meters between samples. * * @author Patrick Murris * @version $Id: LengthMeasurer.java 1171 2013-02-11 21:45:02Z dcollins $ * @see MeasureTool */ public class LengthMeasurer implements MeasurableLength { private static final double DEFAULT_TERRAIN_SAMPLING_STEPS = 128; // number of samples when following terrain private static final double DEFAULT_MAX_SEGMENT_LENGTH = 100e3; // size above which segments are subdivided private static final double DEFAULT_MIN_SEGMENT_LENGTH = 30; // minimum length of a terrain following subdivision private ArrayList positions; private ArrayList subdividedPositions; private boolean followTerrain = false; private int pathType = Polyline.GREAT_CIRCLE; private double maxSegmentLength = DEFAULT_MAX_SEGMENT_LENGTH; private Sector sector; private double lengthTerrainSamplingSteps = DEFAULT_TERRAIN_SAMPLING_STEPS; protected double length = -1; public LengthMeasurer() { } public LengthMeasurer(ArrayList positions) { this.setPositions(positions); } protected void clearCachedValues() { this.subdividedPositions = null; this.length = -1; } public ArrayList getPositions() { return this.positions; } public void setPositions(ArrayList positions, double elevation) { if (positions == null) { String message = Logging.getMessage("nullValue.PositionsListIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } ArrayList newPositions = new ArrayList(); for (LatLon pos : positions) newPositions.add(new Position(pos, elevation)); setPositions(newPositions); } public void setPositions(ArrayList positions) { if (positions == null) { String message = Logging.getMessage("nullValue.PositionsListIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.positions = positions; if (this.positions.size() > 2) this.sector = Sector.boundingSector(this.positions); else this.sector = null; clearCachedValues(); } public boolean isFollowTerrain() { return this.followTerrain; } /** * Set whether measurements should account for terrain deformations. * * @param followTerrain set to true if measurements should account for terrain deformations. */ public void setFollowTerrain(boolean followTerrain) { if (this.followTerrain != followTerrain) { this.followTerrain = followTerrain; clearCachedValues(); } } public int getPathType() { return this.pathType; } /** * Sets the type of path used when subdividing long segments, one of Polyline.GREAT_CIRCLE, which draws segments * as a great circle, Polyline.LINEAR, which determines the intermediate positions between segments by * interpolating the segment endpoints, or Polyline.RHUMB_LINE, which draws segments as a line of constant heading. * * @param pathType the type of path to measure. */ public void setPathType(int pathType) { if (this.pathType != pathType) { this.pathType = pathType; clearCachedValues(); } } /** * Get the maximum length a segment can have before being subdivided along a line following the current pathType. * * @return the maximum length a segment can have before being subdivided. */ public double getMaxSegmentLength() { return this.maxSegmentLength; } /** * Set the maximum length a segment can have before being subdivided along a line following the current pathType. * * @param length the maximum length a segment can have before being subdivided. */ public void setMaxSegmentLength(double length) { if (length <= 0) { String message = Logging.getMessage("generic.ArgumentOutOfRange", length); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.maxSegmentLength != length) { this.maxSegmentLength = length; clearCachedValues(); } } public Sector getBoundingSector() { if (this.sector == null && this.positions != null && this.positions.size() > 2) this.sector = Sector.boundingSector(this.positions); return this.sector; } /** * Returns true if the current position list describe a closed path - one which last position is equal * to the first. * * @return true if the current position list describe a closed path. */ public boolean isClosedShape() { return this.positions != null && this.positions.size() > 1 && this.positions.get(0).equals(this.positions.get(this.positions.size() - 1)); } /** * Get the number of terrain elevation samples used along the path to approximate it's terrain following length. * * @return the number of terrain elevation samples used. */ public double getLengthTerrainSamplingSteps() { return this.lengthTerrainSamplingSteps; } /** * Set the number of terrain elevation samples to be used along the path to approximate it's terrain following * length. * * @param steps the number of terrain elevation samples to be used. */ public void setLengthTerrainSamplingSteps(double steps) { if (steps < 1) { String message = Logging.getMessage("generic.ArgumentOutOfRange", steps); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.lengthTerrainSamplingSteps != steps) { this.lengthTerrainSamplingSteps = steps; this.subdividedPositions = null; this.length = -1; } } /** * Get the path length in meter. * *

If the measurer is set to follow terrain, the computed length will account for terrain deformations as * if someone was walking along that path. Otherwise the length is the sum of the cartesian distance between * each positions.

* * @param globe the globe to draw terrain information from. * @return the current path length or -1 if the position list is too short. */ public double getLength(Globe globe) { if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.length < 0) this.length = this.computeLength(globe, this.followTerrain); return this.length; } // *** Computing length ***************************************************************************** protected double computeLength(Globe globe, boolean followTerrain) { if (this.positions == null || this.positions.size() < 2) return -1; if (this.subdividedPositions == null) { // Subdivide path so as to have at least segments smaller then maxSegmentLenth. If follow terrain, // subdivide so as to have at least lengthTerrainSamplingSteps segments, but no segments shorter then // DEFAULT_MIN_SEGMENT_LENGTH either. double maxLength = this.maxSegmentLength; if (followTerrain) { // Recurse to compute overall path length not following terrain double pathLength = computeLength(globe, !followTerrain); // Determine segment length to have enough sampling points maxLength = pathLength / this.lengthTerrainSamplingSteps; maxLength = Math.min(Math.max(maxLength, DEFAULT_MIN_SEGMENT_LENGTH), getMaxSegmentLength()); } this.subdividedPositions = subdividePositions(globe, this.positions, maxLength, followTerrain, this.pathType); } // Sum each segment length double length = 0; Vec4 p1 = globe.computePointFromPosition(this.subdividedPositions.get(0)); for (int i = 1; i < subdividedPositions.size(); i++) { Vec4 p2 = globe.computePointFromPosition(this.subdividedPositions.get(i)); length += p1.distanceTo3(p2); p1 = p2; } return length; } // // This tries not to use the globe.computePointFromPosition so as to report accurate length on flat globes // // However, it shows significant deviations for east-west measurements. // private double computeSegmentLengthLinearApprox(Globe globe, Position pos1, Position pos2) // { // // Cartesian distance approximation for short segments - only needs globe radius // LatLon midPoint = LatLon.interpolate(.5, pos1, pos2); // double radius = globe.getRadiusAt(midPoint); // double cosLat = Math.cos(midPoint.getLatitude().radians); // return new Vec4( // (pos2.getLongitude().radians - pos1.getLongitude().radians) * radius * cosLat, // (pos2.getLatitude().radians - pos1.getLatitude().radians) * radius, // pos2.getElevation() - pos1.getElevation() // ).getLength3(); // Meters // } /** * Subdivide a list of positions so that no segment is longer then the provided maxLength. * *

If needed, new intermediate positions will be created along lines that follow the given pathType - one of * Polyline.LINEAR, Polyline.RHUMB_LINE or Polyline.GREAT_CIRCLE. All position elevations will be either at the * terrain surface if followTerrain is true, or interpolated according to the original elevations.

* * @param globe the globe to draw elevations and points from. * @param positions the original position list * @param maxLength the maximum length for one segment. * @param followTerrain true if the positions should be on the terrain surface. * @param pathType the type of path to use in between two positions. * @return a list of positions with no segment longer then maxLength and elevations following terrain or not. */ protected static ArrayList subdividePositions(Globe globe, ArrayList positions, double maxLength, boolean followTerrain, int pathType) { return subdividePositions(globe, positions, maxLength, followTerrain, pathType, 0, positions.size()); } /** * Subdivide a list of positions so that no segment is longer then the provided maxLength. Only the positions * between start and start + count - 1 will be processed. * *

If needed, new intermediate positions will be created along lines that follow the given pathType - one of * Polyline.LINEAR, Polyline.RHUMB_LINE or Polyline.GREAT_CIRCLE. All position elevations will be either at the * terrain surface if followTerrain is true, or interpolated according to the original elevations.

* * @param globe the globe to draw elevations and points from. * @param positions the original position list * @param maxLength the maximum length for one segment. * @param followTerrain true if the positions should be on the terrain surface. * @param pathType the type of path to use in between two positions. * @param start the first position indice in the original list. * @param count how many positions from the original list have to be processed and returned. * @return a list of positions with no segment longer then maxLength and elevations following terrain or not. */ protected static ArrayList subdividePositions(Globe globe, ArrayList positions, double maxLength, boolean followTerrain, int pathType, int start, int count) { if (positions == null || positions.size() < start + count) return positions; ArrayList newPositions = new ArrayList(); // Add first position Position pos1 = positions.get(start); if (followTerrain) newPositions.add(new Position(pos1, globe.getElevation(pos1.getLatitude(), pos1.getLongitude()))); else newPositions.add(pos1); for(int i = 1; i < count; i++) { Position pos2 = positions.get(start + i); double arcLengthRadians = LatLon.greatCircleDistance(pos1, pos2).radians; double arcLength = arcLengthRadians * globe.getRadiusAt(LatLon.interpolate(.5, pos1, pos2)); if (arcLength > maxLength) { // if necessary subdivide segment at regular intervals smaller then maxLength Angle segmentAzimuth = null; Angle segmentDistance = null; int steps = (int)Math.ceil(arcLength / maxLength); // number of intervals - at least two for (int j = 1; j < steps; j++) { float s = (float)j / steps; LatLon destLatLon; if (pathType == Polyline.LINEAR) { destLatLon = LatLon.interpolate(s, pos1, pos2); } else if (pathType == Polyline.RHUMB_LINE) { if (segmentAzimuth == null) { segmentAzimuth = LatLon.rhumbAzimuth(pos1, pos2); segmentDistance = LatLon.rhumbDistance(pos1, pos2); } destLatLon = LatLon.rhumbEndPosition(pos1, segmentAzimuth.radians, s * segmentDistance.radians); } else // GREAT_CIRCLE { if (segmentAzimuth == null) { segmentAzimuth = LatLon.greatCircleAzimuth(pos1, pos2); segmentDistance = LatLon.greatCircleDistance(pos1, pos2); } destLatLon = LatLon.greatCircleEndPosition(pos1, segmentAzimuth.radians, s * segmentDistance.radians); } // Set elevation double elevation; if (followTerrain) elevation = globe.getElevation(destLatLon.getLatitude(), destLatLon.getLongitude()); else elevation = pos1.getElevation() * (1 - s) + pos2.getElevation() * s; // Add new position newPositions.add(new Position(destLatLon, elevation)); } } // Finally add the segment end position if (followTerrain) newPositions.add(new Position(pos2, globe.getElevation(pos2.getLatitude(), pos2.getLongitude()))); else newPositions.add(pos2); // Prepare for next segment pos1 = pos2; } return newPositions; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy