package org.codemap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.codemap.internal.DEMAlgorithm;
import org.codemap.internal.MapCaches;
import org.codemap.kdtree.KDException;
import org.codemap.kdtree.KDTree;
import org.codemap.kdtree.KdTreeLookup;
import org.codemap.kdtree.KeySizeException;
import org.codemap.util.MapScheme;
import ch.akuhn.foreach.Collect;
import ch.akuhn.foreach.Each;
public class MapInstance {
private MapCaches caches = new MapCaches(this);
private ConcurrentMap<MapSetting<?>, Object> settings = new ConcurrentHashMap<MapSetting<?>, Object>();
private Collection<Location> locations;
public final int width, height;
private KDTree<Location> kdTree;
private List<Location> DEMLocations;
private MapInstance(Collection<Location> locations, int size, KDTree<Location> tree) {
kdTree = tree;
this.locations = locations;
this.width = this.height = size;
}
public MapInstance(Configuration map, int size, MapScheme<Double> elevation) {
locations = makeLocationsWithSize(map, size, elevation);
kdTree = makeKdTree();
this.width = this.height = size;
}
private KDTree<Location> makeKdTree() {
Set<Location> locs = new TreeSet<Location>();
locs.addAll(locations);
if (locs.isEmpty()) return null;
KDTree<Location> tree = new KDTree<Location>(2);
for(Location each: locs) {
try {
tree.insert(each.getPoint().asDoubleArray(), each);
} catch(KDException e) {
Location found = searchLocation(tree, each.getPoint().asDoubleArray());
System.out.println("duplicate locaiton, toinsert: " + each + "\nfound: " + found);
// throw new RuntimeException(e);
}
}
return tree;
}
private Location searchLocation(KDTree<Location> tree, double[] ds) {
try {
return tree.search(ds);
} catch (KeySizeException e) {
throw new RuntimeException(e);
}
}
public int getWidth() { // TODO rename to getSize()
return width;
}
public <V> V get(Class<? extends MapAlgorithm<V>> key) {
return caches.get(key);
}
public Iterable<Location> locations() {
return locations;
}
private Collection<Location> makeLocationsWithSize(Configuration map, int size, MapScheme<Double> elevation) {
Collection<Location> result = new ArrayList<Location>();
for (Point each: map.points()) {
Double e = elevation.forLocation(each);
if (e == null || Double.isNaN(e.doubleValue())) throw new Error();
result.add(new Location(each, e,
(int) (each.x * size),
(int) (each.y * size)));
}
return result;
}
public MapInstance normalizeElevation() {
double max = maxElevation();
if (max <= 0.0) return this;
Collect<Location> query = Collect.from(locations);
for (Each<Location> each: query) {
each.yield = each.value.withElevation(each.value.getElevation() / max * 100);
}
MapInstance result = new MapInstance(query.getResult(), width, kdTree);
result.settings = new ConcurrentHashMap<MapSetting<?>, Object>(this.settings);
return result;
}
@SuppressWarnings("unchecked")
public <V> V get(MapSetting<V> setting) {
settings.putIfAbsent(setting, setting.defaultValue);
return (V) settings.get(setting);
}
public <V> void reset(MapSetting<V> setting) {
settings.remove(setting);
}
public <V> void set(MapSetting<V> setting, V value) {
settings.put(setting, value);
}
public Location nearestNeighbor(int px, int py) {
return kdTreeNearest(px, py);
}
public Location kdTreeNearest(int px, int py) {
return new KdTreeLookup(kdTree, width).getResult(px, py);
}
public Location naiveNearest(int px, int py) {
int nearestDist2 = Integer.MAX_VALUE;
Location nearestLocation = null;
for (Location each : locations()) {
int dx = each.px - px;
int dy = each.py - py;
int dist2 = dx * dx + dy * dy;
if (dist2 < nearestDist2) {
nearestDist2 = dist2;
nearestLocation = each;
}
}
return nearestLocation;
}
public double maxElevation() {
double max = 0.0;
for (Location each: locations()) {
max = Math.max(max, each.getElevation());
}
return max;
}
public boolean containsPoint(int x, int y) {
return 0 <= x && 0 <= y && x < width && y < height;
}
public boolean isEmpty() {
return locations.isEmpty();
}
public float[][] getDEM() {
return get(DEMAlgorithm.class);
}
public KDTree<Location> getKdTree() {
return kdTree;
}
public Collection<Location> getDEMLocations() {
return DEMLocations != null ? DEMLocations : locations;
}
public void setDEMLoccations(List<Location> result) {
this.DEMLocations = result;
}
}