Projector.java

package org.heigit.ors.fastisochrones.partitioning;

import com.carrotsearch.hppc.IntArrayList;
import com.carrotsearch.hppc.IntHashSet;
import com.graphhopper.storage.GraphHopperStorage;
import org.heigit.ors.fastisochrones.Contour;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

import static org.heigit.ors.fastisochrones.partitioning.FastIsochroneParameters.getSplitValue;
import static org.heigit.ors.fastisochrones.partitioning.Projector.Projection.*;

public class Projector {
    protected static Map<Projection, Projection> correspondingProjMap;
    private GraphHopperStorage ghStorage;
    private static final double TAN_67_5 = 2.414213;
    private static final double TAN_45 = 1;
    private static final double TAN_22_5 = 0.414213;


    public Projector() {
        prepareProjectionMaps();
    }

    private static void prepareProjectionMaps() {
        correspondingProjMap = new EnumMap<>(Projection.class);
        correspondingProjMap.put(LINE_P90, LINE_M00);
        correspondingProjMap.put(LINE_P675, LINE_M225);
        correspondingProjMap.put(LINE_P45, LINE_M45);
        correspondingProjMap.put(LINE_P225, LINE_M675);
        correspondingProjMap.put(LINE_M00, LINE_P90);
        correspondingProjMap.put(LINE_M225, LINE_P675);
        correspondingProjMap.put(LINE_M45, LINE_P45);
        correspondingProjMap.put(LINE_M675, LINE_P225);
    }

    protected Map<Projection, IntArrayList> calculateProjections() {
        //>> Loop through linear combinations and project each Node
        EnumMap<Projection, IntArrayList> nodeListProjMap = new EnumMap<>(Projection.class);
        Integer[] ids = IntStream.rangeClosed(0, ghStorage.getNodes() - 1).boxed().toArray(Integer[]::new);

        Double[] values = new Double[ids.length];
        Sort sort = new Sort();
        for (Projection proj : Projection.values()) {
            //>> sort projected Nodes
            for (int i = 0; i < ids.length; i++) {
                values[i] = proj.sortValue(ghStorage.getNodeAccess().getLat(ids[i]), ghStorage.getNodeAccess().getLon(ids[i]));
            }
            nodeListProjMap.put(proj, sort.sortByValueReturnList(ids, values));
        }
        //Check if there are at least two differing projections. If no lat lon data is set, all projections are the same.
        boolean valid = false;
        for (Projection proj : Projection.values()) {
            if (!nodeListProjMap.get(proj).equals(nodeListProjMap.get(LINE_M00)))
                valid = true;
        }
        if (!valid)
            throw new IllegalStateException("All projections of the graph are the same. Maybe NodeAccess is faulty or not initialized?");
        return nodeListProjMap;
    }

    protected BiPartitionProjection partitionProjections(Map<Projection, IntArrayList> originalProjections, BiPartition biPartition) {
        IntHashSet part0 = biPartition.getPartition(0);
        EnumMap<Projection, IntArrayList> projections0 = new EnumMap<>(Projection.class);
        EnumMap<Projection, IntArrayList> projections1 = new EnumMap<>(Projection.class);
        int origNodeCount = originalProjections.get(Projection.values()[0]).size();
        //Add initial lists
        for (Projection proj : Projection.values()) {
            projections0.put(proj, new IntArrayList(origNodeCount / 3));
            projections1.put(proj, new IntArrayList(origNodeCount / 3));
        }

        //Go through the original projections and separate each into two projections for the subsets, maintaining order
        for (int i = 0; i < origNodeCount; i++) {
            for (Map.Entry<Projection, IntArrayList> proj : originalProjections.entrySet()) {
                int node = proj.getValue().get(i);
                if (part0.contains(node))
                    projections0.get(proj.getKey()).add(node);
                else
                    projections1.get(proj.getKey()).add(node);
            }
        }

        return new BiPartitionProjection(projections0, projections1);
    }

    protected List<Projection> calculateProjectionOrder(Map<Projection, IntArrayList> projections) {
        List<Projection> order;
        EnumMap<Projection, Double> squareRangeProjMap = new EnumMap<>(Projection.class);
        EnumMap<Projection, Double> orthogonalDiffProjMap = new EnumMap<>(Projection.class);
        //>> calculate Projection-Distances
        for (Map.Entry<Projection, IntArrayList> proj : projections.entrySet()) {
            int idx = (int) (proj.getValue().size() * getSplitValue());
            squareRangeProjMap.put(proj.getKey(), projIndividualValue(projections, proj.getKey(), idx));
        }

        //>> combine inverse Projection-Distances
        for (Projection proj : projections.keySet()) {
            orthogonalDiffProjMap.put(proj, projCombinedValue(squareRangeProjMap, proj));
        }

        //>> order Projections by Projection-Value
        Sort sort = new Sort();
        order = sort.sortByValueReturnList(orthogonalDiffProjMap, false);
        return order;
    }

    private double projIndividualValue(Map<Projection, IntArrayList> projMap, Projection proj, int idx) {
        IntArrayList tmpNodeList = projMap.get(proj);
        double toLat = ghStorage.getNodeAccess().getLat(tmpNodeList.get(idx));
        double toLon = ghStorage.getNodeAccess().getLon(tmpNodeList.get(idx));
        double fromLat = ghStorage.getNodeAccess().getLat(tmpNodeList.get(tmpNodeList.size() - idx - 1));
        double fromLon = ghStorage.getNodeAccess().getLon(tmpNodeList.get(tmpNodeList.size() - idx - 1));

        return Contour.distance(fromLat, toLat, fromLon, toLon);
    }

    private double projCombinedValue(Map<Projection, Double> squareRangeProjMap, Projection proj) {
        return squareRangeProjMap.get(proj) * squareRangeProjMap.get(proj) / squareRangeProjMap.get(correspondingProjMap.get(proj));
    }

    public void setGHStorage(GraphHopperStorage ghStorage) {
        this.ghStorage = ghStorage;
    }

    public enum Projection {
        LINE_P90 {
            public double sortValue(double lat, double lon) {
                return lat;
            }
        },
        LINE_P675 {
            public double sortValue(double lat, double lon) {
                return lat + TAN_67_5 * lon;
            }
        },
        LINE_P45 {
            public double sortValue(double lat, double lon) {
                return lat + TAN_45 * lon;
            }
        },
        LINE_P225 {
            public double sortValue(double lat, double lon) {
                return lat + TAN_22_5 * lon;
            }
        },
        LINE_M00 {
            public double sortValue(double lat, double lon) {
                return lon;
            }
        },
        LINE_M225 {
            public double sortValue(double lat, double lon) {
                return lat - TAN_22_5 * lon;
            }
        },
        LINE_M45 {
            public double sortValue(double lat, double lon) {
                return lat - TAN_45 * lon;
            }
        },
        LINE_M675 {
            public double sortValue(double lat, double lon) {
                return lat - TAN_67_5 * lon;
            }
        };

        abstract double sortValue(double lat, double lon);
    }
}