/*
* 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 java.util.ArrayList;
import java.util.Arrays;
/**
* Used to replace HashMap when keeping track of nodes, as HashMap is too slow.
* This implementation is 1:1
*
* @author Alec
*/
public class NodeMap {
private int lastIndex; //should always be and empty index
private long[] keys;
private Coordinate[] values;
public NodeMap() {
init(1000);
lastIndex = 1;
}
public NodeMap(int size) {
lastIndex = 1;
if (size > 0) {
init(size);
} else {
init(1000);
}
}
/**
* Returns the Key assigned to a given value;
*
* @param value
* @return Returns -1 if no coordinate is found.
*/
public long findKey(Coordinate value) {
Coordinate c;
try {
for (int i = 0; i < lastIndex; i++) {
c = values[i];
if (c != null) {
if (c.equals(value))
return keys[i];
} else {
return -1;
}
}
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in NodeMap.findKey(Coordinate) - " + e);
}
return 0;
}
/**
* Returns a coordinate with a given Key.
*
* @param key
* @return
*/
public Coordinate get(long key) {
Coordinate c;
int index;
try {
index = Arrays.binarySearch(keys, 0, lastIndex, key);
if (index >= 0) {
c = values[index];
c.incrementPullCount();
return c;
} else {
index = Math.abs(index);
if (index < lastIndex) {
if (keys[index] == key) {
c = values[index];
c.incrementPullCount();
return c;
} else {
for (int i = lastIndex - 1; i > 0; i--) {
if (keys[i] == key) {
c = values[i];
c.incrementPullCount();
return c;
}
}
return null;
}
} else {
return null;
}
}
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in NodeMap.get(long) - " + e);
return null;
}
}
/**
* Returns an Array of all the Coordinates in this NodeMap.
*
* @return
*/
public Coordinate[] getAllCoordinates() {
return values;
}
/**
* Returns a LatLonBox that bounds all the coordinates in this NodeMap.
*
* @return
*/
public LatLonBox getBounds() {
double north = -90;
double south = 90;
double east = -180;
double west = 180;
for (int i = 0; i < lastIndex; i++) {
Coordinate c = values[i];
if (c.getLatitude() >= -90 && c.getLatitude() <= 90) {
if (c.getLatitude() > north) north = c.getLatitude();
if (c.getLatitude() < south) south = c.getLatitude();
}
if (c.getLongitude() >= -180 && c.getLongitude() <= 180) {
if (c.getLongitude() < west) west = c.getLongitude();
if (c.getLongitude() > east) east = c.getLongitude();
}
}
return new LatLonBox((float) north, (float) south, (float) east, (float) west);
}
/**
* Returns an ArrayList<Coordinate> of the Coordinates contained in the
* given LatLonBox boundary.
*
* @param boundary
* @return
*/
public ArrayList<Coordinate> getCoordinatesWithinBoundary(LatLonBox boundary) {
ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
Coordinate c;
for (int i = 0; i < lastIndex; i++) {
c = values[i];
if (boundary.contains(c))
coordinates.add(c);
}
return coordinates;
}
/**
* Returns the coordinate at a given Index;
*
* @param index
* @return
*/
public Coordinate getFromIndex(int index) {
Coordinate c = values[index];
if (c != null)
c.incrementPullCount();
return c;
}
/**
* Returns the Key at a given Index
*
* @param index
* @return
*/
public long getKeyFromIndex(int index) {
return keys[index];
}
/**
* Returns the array index for a given key.
*
* @param key
* @return
*/
public int getKeyIndex(long key) {
return Arrays.binarySearch(keys, 0, lastIndex, key);
}
/**
* Returns the Value at a given index
*
* @param index
* @return
*/
public Coordinate getValueFromIndex(int index) {
Coordinate c = values[index];
c.incrementPullCount();
return c;
}
private void growAtEnd(int required) {
long[] newKeys = new long[required];
Coordinate[] newValues = new Coordinate[required];
System.arraycopy(keys, 0, newKeys, 0, lastIndex);
System.arraycopy(values, 0, newValues, 0, lastIndex);
this.keys = newKeys;
this.values = newValues;
}
private void growForInsert(int location, int required) {
long[] newKeys = new long[required];
Coordinate[] newValues = new Coordinate[required];
System.arraycopy(keys, 0, newKeys, 0, location);
System.arraycopy(values, 0, newValues, 0, location);
System.arraycopy(keys, location, newKeys, location + 1, lastIndex - location);
System.arraycopy(values, location, newValues, location + 1, lastIndex - location);
keys = newKeys;
values = newValues;
}
private void init(int size) {
lastIndex = 1;
keys = new long[size];
values = new Coordinate[size];
keys[0] = 0;
values[0] = Coordinate.UNKNOWN_COORDINATE;
}
private void insert(int index, long key, Coordinate value) {
if (lastIndex + 1 < keys.length) {
long[] newKeys = new long[keys.length];
Coordinate[] newValues = new Coordinate[values.length];
System.arraycopy(keys, 0, newKeys, 0, index);
System.arraycopy(values, 0, newValues, 0, index);
System.arraycopy(keys, index, newKeys, index + 1, lastIndex - index);
System.arraycopy(values, index, newValues, index + 1, lastIndex - index);
keys = newKeys;
values = newValues;
keys[index] = key;
values[index] = value;
lastIndex++;
} else {
growForInsert(index, lastIndex + 50);
keys[index] = key;
values[index] = value;
lastIndex++;
}
}
/**
* Returns if the Node Map is empty or not;
*
* @return
*/
public boolean isEmpty() {
if (lastIndex == 1) {
return true;
} else {
return false;
}
}
/**
* Adds a coordinate to this Map. A key is generated from the last index.
*
* @param value
*/
public void put(Coordinate value) {
if (value != null) {
if (value.getID() > 0) {
put(value.getID(), value);
} else {
put((keys[lastIndex - 1] + 1), value);
}
} else {
Logger.log(Logger.ERR, "NodeMap.put(Coordinate) - Null Coordinate");
}
}
/**
* Adds a coordinate with a given key to this Map.
*
* @param key
* @param value
*/
public void put(long key, Coordinate value) {
boolean keyInserted;
int currentIndex;
long currentKey;
try {
keyInserted = false;
value.setId(key);
if (lastIndex == 1) {
keys[lastIndex] = key;
values[lastIndex] = value;
lastIndex++;
} else {
currentKey = keys[lastIndex - 1];
//Check to see if we can just add to the end, which should be the case most of the time.
if (key > currentKey) {
keys[lastIndex] = key;
values[lastIndex] = value;
if ((lastIndex + 1) < keys.length) {
//no need to grow array, just increment last index
lastIndex++;
} else {
lastIndex++;
growAtEnd(lastIndex + 500);
}
} else {
currentIndex = Arrays.binarySearch(keys, 0, lastIndex, key);
if (currentIndex > 0) {
currentKey = keys[currentIndex];
if (currentKey != key) {
insert(currentIndex, key, value);
} else {
//Key already exists
if (values[currentIndex].equals(value)) {
//coordinates are the same, do nothing
} else {
//overwrite/update Coordinate
Logger.log(Logger.INFO, "NodeMap - Node Updated, old: " + values[currentIndex] + " new: " + value);
//Put the new values in the old Coordinate
values[currentIndex].update((float) value.getAltitude(), (float) value.getLatitude(), (float) value.getLongitude(), value.getTimestampValue());
//Upate the reference of the new Coordinate to the now update 'old' Coordinate.
value = values[currentIndex];
}
}
} else if (currentIndex < 0) {
//Key not present insert it.
currentIndex = Math.abs(currentIndex) - 1;
if (keys[currentIndex - 1] < key) {
//we found the right place for it.
insert(currentIndex, key, value);
} else {
//we didn't find the right place for it, search.
for (int i = lastIndex - 1; i >= 0; i--) {
if (i == 0) {
insert(i+1, key, value);
keyInserted = true;
break;
} else if (key > keys[i]) {
insert(i+1, key, value);
keyInserted = true;
break;
}
}
if (keyInserted == false)
Logger.log(Logger.ERR, "Error in NodeMap.put(long, Coordinate) - Insert Error Key: " + key);
}
} else {
//something went wrong
Logger.log(Logger.ERR, "Error in NodeMap.put(long, Coordinate) - Insert Error Key: " + key);
}
}
}
if (keys.length == lastIndex)
growAtEnd(lastIndex + 500);
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in NodeMap.put(long, Coordinate) - " + e + " (" + key + " , " + value.toString() + ")");
}
}
/**
* Returns the number of elements in this map.
*
* @return
*/
public int size() {
return lastIndex - 1;
}
}