RouteExtraInfo.java

/*  This file is part of Openrouteservice.
 *
 *  Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.

 *  This library 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 Lesser General Public License for more details.

 *  You should have received a copy of the GNU Lesser General Public License along with this library;
 *  if not, see <https://www.gnu.org/licenses/>.
 */
package org.heigit.ors.routing;

import com.graphhopper.storage.GraphExtension;
import org.heigit.ors.common.DistanceUnit;
import org.heigit.ors.exceptions.StatusCodeException;
import org.heigit.ors.routing.graphhopper.extensions.storages.WarningGraphExtension;
import org.heigit.ors.util.DistanceUnitUtil;
import org.heigit.ors.util.FormatUtility;

import java.util.*;

public class RouteExtraInfo {
    private final String name;
    private final List<RouteSegmentItem> segments;
    private double factor = 1.0;
    private boolean usedForWarnings = false;
    private WarningGraphExtension warningGraphExtension;

    public RouteExtraInfo(String name) {
        this(name, null);
    }

    /**
     * Constructor that can mark the RouteExtraInfo as being able to generate warnings or not
     *
     * @param name      name of the extra info
     * @param extension The GraphExtension that is used to generate the extra info. A check is made against this to
     *                  see if it is of type {@Link org.heigit.ors.routing.graphhopper.extensions.storages.WarningGraphExtension}.
     */
    public RouteExtraInfo(String name, GraphExtension extension) {
        this.name = name;
        segments = new ArrayList<>();
        if (extension instanceof WarningGraphExtension graphExtension) {
            warningGraphExtension = graphExtension;
            usedForWarnings = true;
        }
    }

    public String getName() {
        return name;
    }

    public boolean isEmpty() {
        return segments.isEmpty();
    }

    public void add(RouteSegmentItem item) {
        segments.add(item);
    }

    public List<RouteSegmentItem> getSegments() {
        return segments;
    }

    public List<ExtraSummaryItem> getSummary(DistanceUnit units, double routeDistance, boolean sort) throws StatusCodeException {
        List<ExtraSummaryItem> summary = new ArrayList<>();

        if (!segments.isEmpty()) {
            Comparator<ExtraSummaryItem> comp = (ExtraSummaryItem a, ExtraSummaryItem b) -> Double.compare(b.getAmount(), a.getAmount());

            double totalDist = 0.0;

            Map<Double, Double> stats = new HashMap<>();

            for (RouteSegmentItem seg : segments) {
                Double scaledValue = seg.getValue() / factor;
                Double value = stats.get(scaledValue);

                if (value == null)
                    stats.put(scaledValue, seg.getDistance());
                else {
                    value += seg.getDistance();
                    stats.put(scaledValue, value);
                }

                totalDist += seg.getDistance();
            }

            if (totalDist != 0.0) {
                int unitDecimals = FormatUtility.getUnitDecimals(units);
                // Some extras such as steepness might provide inconsistent distance values caused by multiple rounding.
                // Therefore, we try to scale distance so that their sum equals to the whole distance of a route
                double distScale = totalDist / routeDistance;

                for (Map.Entry<Double, Double> entry : stats.entrySet()) {
                    double scaledValue = entry.getValue() / distScale;
                    ExtraSummaryItem esi = new ExtraSummaryItem(entry.getKey(),
                            FormatUtility.roundToDecimals(DistanceUnitUtil.convert(scaledValue, DistanceUnit.METERS, units), unitDecimals),
                            FormatUtility.roundToDecimals(scaledValue * 100.0 / routeDistance, 2)
                    );

                    summary.add(esi);
                }

                if (sort)
                    summary.sort(comp);
            }
        }

        return summary;
    }

    public double getFactor() {
        return factor;
    }

    public void setFactor(double factor) {
        this.factor = factor;
    }

    public boolean isUsedForWarnings() {
        return usedForWarnings;
    }

    public WarningGraphExtension getWarningGraphExtension() {
        return warningGraphExtension;
    }
}