/* * Copyright (C) 2014 Alec Dhuse * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package co.foldingmap.map.vector; import co.foldingmap.Logger; import co.foldingmap.map.MapObject; import java.io.Serializable; import java.util.*; /** * This is a list object with use specific to the Coordinate class. * It includes useful functions for dealing with Coordinates. * This class is for use in MapObjects for storing the objects Coordinates. * * @author Alec */ public class CoordinateList<Coordinate> extends AbstractList<Coordinate> implements List<Coordinate>, Cloneable, Serializable, RandomAccess { private MapObject parentObject; private transient int firstIndex; private transient int lastIndex; private transient Coordinate[] array; /** * Constructs a new instance of CoordinateList with ten capacity. */ public CoordinateList() { this(10); } /** * Constructs a new instance of CoordinateList with the specified capacity. * * @param capacity * the initial capacity of this CoordinateList. */ public CoordinateList(int capacity) { if (capacity < 0) { throw new IllegalArgumentException(); } firstIndex = lastIndex = 0; array = newElementArray(capacity); } /** * Constructs a new instance given an ArrayList<Coordinate> * * @param coordinates */ public CoordinateList(ArrayList<Coordinate> coordinates) { this.array = (Coordinate[]) coordinates.toArray(); firstIndex = 0; lastIndex = array.length; } /** * Constructs a new instance given an Array of Coordinates. * * @param coordinates */ public CoordinateList(Coordinate[] coordinates) { Object[] tempArray = new Object[coordinates.length]; this.array = (Coordinate[]) tempArray; System.arraycopy(coordinates, 0, this.array, 0, coordinates.length); firstIndex = 0; lastIndex = coordinates.length; } /** * Inserts the specified object into this {@code CoordinateList} at the * specified location. The object is inserted before any previous element * at the specified location. If the location is equal to the size of this * {@code ArrayList}, the object is added at the end. * * @param location * the index at which to insert the object. * @param object * the object to add. * @throws IndexOutOfBoundsException * when {@code location < 0 || > size()} */ @Override public void add(int location, Coordinate object) { int size = lastIndex - firstIndex; if (0 < location && location < size) { if (firstIndex == 0 && lastIndex == array.length) { growForInsert(location, 1); } else if ((location < size / 2 && firstIndex > 0) || lastIndex == array.length) { System.arraycopy(array, firstIndex, array, --firstIndex, location); } else { int index = location + firstIndex; System.arraycopy(array, index, array, index + 1, size - location); lastIndex++; } array[location + firstIndex] = object; } else if (location == 0) { if (firstIndex == 0) { growAtFront(1); } array[--firstIndex] = object; } else if (location == size) { if (lastIndex == array.length) { growAtEnd(1); } array[lastIndex++] = object; } else { throw new IndexOutOfBoundsException("Index: " + Integer.valueOf(location) + " Size: " + Integer.valueOf(lastIndex - firstIndex)); } modCount++; } /** * Adds the specified object at the end of this CoordinateList unless an * instance already exists in the list. * * @param object * The Coordinate to add. * @return * If the object was added or not. */ @Override public boolean add(Coordinate newCoordinate) { try { boolean instanceFound = false; if (firstIndex != lastIndex) { //check to see if the coordinate exists already for (int i = firstIndex; i < lastIndex; i++) { if (array[i].equals(newCoordinate)) { instanceFound = true; break; } } //end for loop } if (instanceFound == false) { forceAdd(newCoordinate); return true; } else { return false; } } catch (Exception e) { System.err.println("Error in CoordinateList.add() - " + e); return false; } } /** * Adds the objects in the specified collection to this * {@code CoordinateList}. * * @param collection * the collection of objects. * @return {@code true} if this {@code CoordinateList} is modified, * {@code false} otherwise. */ @Override public boolean addAll(Collection<? extends Coordinate> collection) { Object[] dumpArray = collection.toArray(); if (dumpArray.length == 0) { return false; } if (dumpArray.length > array.length - lastIndex) { growAtEnd(dumpArray.length); } System.arraycopy(dumpArray, 0, this.array, lastIndex, dumpArray.length); lastIndex += dumpArray.length; modCount++; return true; } /** * Returns a new {@code CoordinateList} with the same elements, the same * size and the same capacity as this {@code CoordinateList}. * * @return a shallow copy of this {@code CoordinateList} * @see java.lang.Cloneable */ @Override @SuppressWarnings("unchecked") public CoordinateList clone() { try { CoordinateList<Coordinate> newList = (CoordinateList<Coordinate>) super.clone(); newList.array = array.clone(); return newList; } catch (CloneNotSupportedException e) { return null; } } /** * Returns if the supplied coordinate already exists in the list. * * @param c * The Coordinate to check. * * @return If the Coordinate exist in the list or not. */ public boolean contains(co.foldingmap.map.vector.Coordinate c) { boolean result = false; co.foldingmap.map.vector.Coordinate currentCoordinate; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate == c) { result = true; break; } } return result; } /** * Returns if this Coordinate list has the same coordinates as another list. * * @param o * @return */ @Override public boolean equals(Object o) { if (o instanceof CoordinateList) { CoordinateList cl = (CoordinateList) o; return (this.hashCode() == cl.hashCode()); } else { return false; } } /** * Hash code generation for this CoordinateList. * * @return */ @Override public int hashCode() { Coordinate c; int hash = 7; for (int i = firstIndex; i < lastIndex; i++) { c = array[i]; hash = 22 * hash + (c != null ? c.hashCode() : 0); } return hash; } /** * Adds the specified object at the end of this CoordinateList even if an * instance already exists in the list. * * @param newCoordinate * the Coordinate to add. * @return always true */ public boolean forceAdd(Coordinate newCoordinate) { try { if (lastIndex == array.length) { growAtEnd(1); } array[lastIndex++] = newCoordinate; modCount++; return true; } catch (Exception e) { System.err.println("Error in CoordinateList.forceAdd(Coordinate) - " + e); return false; } } /** * Returns the Coordinate at the given list location. * * @param location * @return */ @Override public Coordinate get(int location) { if (0 <= location && location < (lastIndex - firstIndex)) { return array[firstIndex + location]; } throw new IndexOutOfBoundsException("Index: " + Integer.valueOf(location) + " List Size: " + Integer.valueOf(lastIndex - firstIndex)); } /** * Returns all coordinates in this list that are within the LatLonAltBox. * Altitude is ignored. * * @param bounds * @return */ public CoordinateList<Coordinate> get(LatLonAltBox bounds) { CoordinateList coordinates; co.foldingmap.map.vector.Coordinate currentCoordinate; coordinates = new CoordinateList(); for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (bounds.contains(currentCoordinate)) coordinates.add(currentCoordinate); } return coordinates; } /** * Returns a float array containing the altitudes of each coordinate. * Indexes of the float array are the same as this CoordinateList. * * @return */ public float[] getAltitudes() { co.foldingmap.map.vector.Coordinate c; float[] altitudes; altitudes = new float[this.size()]; for (int i = 0; i < this.size(); i++) { c = (co.foldingmap.map.vector.Coordinate) this.get(i); altitudes[i] = c.getAltitude(); } return altitudes; } /** * Returns this CoordinateList as an ArrayList * * @return */ public ArrayList<Coordinate> getArrayList() { ArrayList<Coordinate> arrayList; arrayList = new ArrayList<Coordinate>(this); return arrayList; } /** * Returns a list of coordinates between two coordinates that exist in this * CoordinateList. The returned list will incluse the start and end * Coordinate Parameters. * * @param c1 Start Coordinate * @param c2 End Coordinate * @return */ public CoordinateList<co.foldingmap.map.vector.Coordinate> getCoordinatesBetween(co.foldingmap.map.vector.Coordinate c1, co.foldingmap.map.vector.Coordinate c2) { CoordinateList<co.foldingmap.map.vector.Coordinate> results; int c1Index, c2Index; co.foldingmap.map.vector.Coordinate currentCoordinate; results = new CoordinateList<co.foldingmap.map.vector.Coordinate>(); c1Index = indexOf(c1); c2Index = indexOf(c2); if (c1Index < c2Index) { for (int i = c1Index; i <= c2Index; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; results.add(currentCoordinate); } } else { for (int i = c2Index; i >= c1Index; i--) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; results.add(currentCoordinate); } } return results; } /** * Returns the Coordinate closest to the coordinate provided. * * @param c The Coordinate to find the closest. * @return The Coordinate in this list closest to the Coordinate provided. */ public co.foldingmap.map.vector.Coordinate getCoordinateClosestTo(co.foldingmap.map.vector.Coordinate c) { co.foldingmap.map.vector.Coordinate closestCoordinate; co.foldingmap.map.vector.Coordinate currentCoordinate; double closestDistance, currentDistance; Object currentObject; closestCoordinate = co.foldingmap.map.vector.Coordinate.UNKNOWN_COORDINATE; closestDistance = Double.MAX_VALUE; try { for (int i = firstIndex; i < lastIndex; i++) { currentObject = array[i]; currentCoordinate = (co.foldingmap.map.vector.Coordinate) currentObject; if (currentCoordinate != null) { currentDistance = CoordinateMath.getDistance(c, currentCoordinate); if (currentDistance < closestDistance) { closestDistance = currentDistance; closestCoordinate = currentCoordinate; } } } } catch (Exception e) { System.err.println("Error in CoordinateList.getCoordinateClosestTo(Coordinate) - " + e); } return closestCoordinate; } /** * Returns the index in the list of a given Coordinate. * * @param c * @return */ public int indexOf(co.foldingmap.map.vector.Coordinate c) { co.foldingmap.map.vector.Coordinate currentCoordinate; int index = -1; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate == c) { index = i; break; } } return index; } /** * Returns this List's Coordinates as a String of the form: * longitude,latitude,altitude longitude,latitude,altitude * * @return */ public String getCoordinateString() { co.foldingmap.map.vector.Coordinate currentCoordinate; StringBuilder coord; coord = new StringBuilder(); try { for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate.getID() == 0) { coord.append(currentCoordinate.toString()); } else { coord.append(currentCoordinate.getID()); } if (i < (lastIndex - 1)) coord.append(" "); } } catch (Exception e) { System.err.println("Error in CoordinateList.getCoordinateString() - " + e); } return coord.toString(); } /** * Returns the Earliest date of a Coordinate in this CoordinateList. * @return */ public long getEarliestDate() { long earliest; co.foldingmap.map.vector.Coordinate currentCoordinate; earliest = Long.MAX_VALUE; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate.getDate() < earliest) earliest = currentCoordinate.getDate(); } return earliest; } /** * Returns the Eastern most coordinate in this CoordinateList. * @return */ public double getEasternMostLongitude() { double longitude; co.foldingmap.map.vector.Coordinate currentCoordinate; longitude = -180f; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate != null) { if (currentCoordinate.getLongitude() > longitude) longitude = currentCoordinate.getLongitude(); } } return longitude; } /** * Returns the latest date of a Coordinate in this CoordinateList. * * @return */ public long getLatestDate() { long latest; co.foldingmap.map.vector.Coordinate currentCoordinate; latest = getEarliestDate(); for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate.getDate() > latest) latest = currentCoordinate.getDate(); } return latest; } /** * Returns the Maximum Altitude of any Coordinate in this list. * * @return */ public float getMaxAltitude() { float minAltitude; co.foldingmap.map.vector.Coordinate currentCoordinate; minAltitude = Float.MIN_VALUE; for (int i = firstIndex; i <lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate != null) { if (currentCoordinate.getAltitude() > minAltitude) minAltitude = currentCoordinate.getAltitude(); } } return minAltitude; } /** * Returns the Minimum Altitude of any Coordinate in this list. * * @return */ public float getMinAltitude() { float minAltitude; co.foldingmap.map.vector.Coordinate currentCoordinate; minAltitude = Float.MAX_VALUE; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate != null) { if (currentCoordinate.getAltitude() < minAltitude) minAltitude = currentCoordinate.getAltitude(); } } return minAltitude; } /** * Returns the Northern most Latitude of any coordinate in this list. * @return */ public double getNorthernMostLatitude() { double latitude; co.foldingmap.map.vector.Coordinate currentCoordinate; latitude = -90f; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate != null) { if (currentCoordinate.getLatitude() > latitude) latitude = currentCoordinate.getLatitude(); } } return latitude; } /** * Returns an array containing the same coordinates as this CoordinateList, * but in reverse order of the coordinates in this list. * * @return */ public CoordinateList<Coordinate> getReverse() { Coordinate[] newArray = newElementArray(this.size()); int newArrayIndex = 0; try { for (int i = (lastIndex - 1); i >= firstIndex; i--) { newArray[newArrayIndex] = array[i]; newArrayIndex++; } } catch (Exception e) { Logger.log(Logger.ERR, "Error in CoordinateList.getReverse() - " + e); } return new CoordinateList(newArray); } /** * Calculates the length of segments for Coordinates in this list. * * @return */ public ArrayList<Float> getSegmentLengths() { ArrayList<Float> lengths; Float length; co.foldingmap.map.vector.Coordinate coordinate1; co.foldingmap.map.vector.Coordinate coordinate2; lengths = new ArrayList<Float>(); for (int i = (firstIndex + 1); i < lastIndex; i++) { coordinate1 = (co.foldingmap.map.vector.Coordinate) array[i]; coordinate2 = (co.foldingmap.map.vector.Coordinate) array[i-1]; length = new Float(CoordinateMath.getDistance(coordinate1, coordinate2)); lengths.add(length); } return lengths; } /** * Returns the Southern most Latitude of any Coordinate in this list * @return */ public double getSouthernMostLatitude() { double latitude; co.foldingmap.map.vector.Coordinate currentCoordinate; latitude = 90; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate != null) { if (currentCoordinate.getLatitude() < latitude) latitude = currentCoordinate.getLatitude(); } } return latitude; } /** * Returns the Western most Longitude of any Coordinate in this list. * * @return */ public double getWesternMostLongitude() { double longitude; co.foldingmap.map.vector.Coordinate currentCoordinate; longitude = 180f; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate != null) { if (currentCoordinate.getLongitude() < longitude) longitude = currentCoordinate.getLongitude(); } } return longitude; } private void growAtEnd(int required) { int size = lastIndex - firstIndex; if (firstIndex >= required - (array.length - lastIndex)) { int newLast = lastIndex - firstIndex; if (size > 0) { System.arraycopy(array, firstIndex, array, 0, size); int start = newLast < firstIndex ? firstIndex : newLast; Arrays.fill(array, start, array.length, null); } firstIndex = 0; lastIndex = newLast; } else { int increment = size / 2; if (required > increment) increment = required; if (increment < 12) increment = 12; Coordinate[] newArray = newElementArray(size + increment); if (size > 0) { System.arraycopy(array, firstIndex, newArray, 0, size); firstIndex = 0; lastIndex = size; } array = newArray; } } private void growAtFront(int required) { int size = lastIndex - firstIndex; if (array.length - lastIndex + firstIndex >= required) { int newFirst = array.length - size; if (size > 0) { System.arraycopy(array, firstIndex, array, newFirst, size); int length = firstIndex + size > newFirst ? newFirst : firstIndex + size; Arrays.fill(array, firstIndex, length, null); } firstIndex = newFirst; lastIndex = array.length; } else { int increment = size / 2; if (required > increment) { increment = required; } if (increment < 12) { increment = 12; } Coordinate[] newArray = newElementArray(size + increment); if (size > 0) { System.arraycopy(array, firstIndex, newArray, newArray.length - size, size); } firstIndex = newArray.length - size; lastIndex = newArray.length; array = newArray; } } private void growForInsert(int location, int required) { int size = lastIndex - firstIndex; int increment = size / 2; if (required > increment) { increment = required; } if (increment < 12) { increment = 12; } Coordinate[] newArray = newElementArray(size + increment); int newFirst = increment - required; // Copy elements after location to the new array skipping inserted elements System.arraycopy(array, location + firstIndex, newArray, newFirst + location + required, size - location); // Copy elements before location to the new array from firstIndex System.arraycopy(array, firstIndex, newArray, newFirst, location); firstIndex = newFirst; lastIndex = size + increment; array = newArray; } /** * Returns if this CoordinateList contains and Coordinates that are shared * with other objects. * * @return */ public boolean hasSharedCoordinates() { boolean hasShared; co.foldingmap.map.vector.Coordinate currentCoordinate; hasShared = false; for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; if (currentCoordinate.isShared()) { hasShared = true; break; } } return hasShared; } /** * Checks to see if the supplied coordinate is at the beginning or end of * this coordinate list. * * @param c * The Coordinate to test. * @return If the coordinate is an endpoint. */ public boolean isEndPoint(Coordinate c) { if (c.equals(array[firstIndex])) { return true; } else if (c.equals(array[lastIndex - 1])) { return true; } else { return false; } } /** * Returns the last Coordinate in this list * * @return */ public Coordinate lastCoordinate() { return array[lastIndex - 1]; } @SuppressWarnings("unchecked") private Coordinate[] newElementArray(int size) { return (Coordinate[]) new Object[size]; } /** * Removes the object at the specified location from this list. * * @param location * the index of the object to remove. * @return the removed object. * @throws IndexOutOfBoundsException * when {@code location < 0 || >= size()} */ @Override public Coordinate remove(int location) { Coordinate result; int size = lastIndex - firstIndex; if (0 <= location && location < size) { if (location == size - 1) { result = array[--lastIndex]; array[lastIndex] = null; } else if (location == 0) { result = array[firstIndex]; array[firstIndex++] = null; } else { int elementIndex = firstIndex + location; result = array[elementIndex]; if (location < size / 2) { System.arraycopy(array, firstIndex, array, firstIndex + 1, location); array[firstIndex++] = null; } else { System.arraycopy(array, elementIndex + 1, array, elementIndex, size - location - 1); array[--lastIndex] = null; } } if (firstIndex == lastIndex) { firstIndex = lastIndex = 0; } } else { throw new IndexOutOfBoundsException(Integer.valueOf(location) + " List size: " + Integer.valueOf(lastIndex - firstIndex)); } modCount++; return result; } /** * Removes the given MapObject parent, from the Coordinates in this * CoordinateList. Not all coordinateLists have to have a parent. * * @param parent */ public void removeParentObject(MapObject parent) { co.foldingmap.map.vector.Coordinate currentCoordinate; if (this.parentObject == parent) this.parentObject = null; //update the parent for all Coordinates containd in this list. for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; currentCoordinate.removeParent(parent); } } /** * Reverses the order of the coordinates in this list. */ public void reverse() { Coordinate[] newArray = newElementArray(array.length); int newArrayIndex = firstIndex; for (int i = (lastIndex - 1); i >= firstIndex; i--) { newArray[newArrayIndex] = array[i]; newArrayIndex++; } array = newArray; } /** * Sets the MapObject that is the parent, the object using this * CoordinateList. Not all coordinateLists have to have a parent. * * @param parent */ public void setParentObject(MapObject parent) { co.foldingmap.map.vector.Coordinate currentCoordinate; this.parentObject = parent; //update the parent for all Coordinates containd in this list. for (int i = firstIndex; i < lastIndex; i++) { currentCoordinate = (co.foldingmap.map.vector.Coordinate) array[i]; currentCoordinate.addParent(parent); } } /** * Returns the number of elements in this CoordinateList. * * @return the number of elements in this CoordinateList. */ @Override public int size() { return lastIndex - firstIndex; } }