jflowmap.es_agg.EdgeSegment.java Source code

Java tutorial

Introduction

Here is the source code for jflowmap.es_agg.EdgeSegment.java

Source

/*
 * This file is part of JFlowMap.
 *
 * Copyright 2009 Ilya Boyandin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jflowmap.es_agg;

import java.util.Collections;
import java.util.List;

import jflowmap.geom.FPoint;
import jflowmap.geom.GeomUtils;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
 * EdgeSegment should be mutable, otherwise it's difficult to propagate
 * to the cluster tree  the changes made to the segments adjacent to
 * those which are merged.
 *
 * @author Ilya Boyandin
 */
public class EdgeSegment {

    private FPoint a;
    private FPoint b;
    private final double weight;
    private final List<SegmentedEdge> parents = Lists.newArrayList();

    public EdgeSegment(FPoint a, FPoint b, double weight) {
        this.a = a;
        this.b = b;
        this.weight = weight;
    }

    public EdgeSegment(FPoint a, FPoint b, double weight, SegmentedEdge parent) {
        this(a, b, weight);
        this.parents.add(parent);
    }

    public EdgeSegment(FPoint a, FPoint b, double weight, Iterable<SegmentedEdge> parents) {
        this(a, b, weight);
        Iterables.addAll(this.parents, parents);
    }

    public FPoint getA() {
        return a;
    }

    public FPoint getB() {
        return b;
    }

    public void setA(FPoint newA) {
        if (newA.equals(a)) {
            return;
        }
        if (a.isFixed()) {
            throw new IllegalStateException("A is a fixed point");
        }
        this.a = newA;
        for (SegmentedEdge se : parents) {
            EdgeSegment prev = se.getPrev(this);
            if (prev != null) {
                prev.setB(newA);
            }
        }
    }

    public void setB(FPoint newB) {
        if (newB.equals(b)) {
            return;
        }
        if (b.isFixed()) {
            throw new IllegalStateException("B is a fixed point");
        }
        this.b = newB;
        for (SegmentedEdge se : parents) {
            EdgeSegment next = se.getNext(this);
            if (next != null) {
                next.setA(newB);
            }
        }
    }

    public double getWeight() {
        return weight;
    }

    public List<SegmentedEdge> getParents() {
        return Collections.unmodifiableList(parents);
    }

    public void addParent(SegmentedEdge parent) {
        if (!parents.contains(parent)) {
            parents.add(parent);
        }
    }

    public void removeParent(SegmentedEdge parent) {
        parents.remove(parent);
    }

    public boolean isConsecutiveFor(EdgeSegment seg) {
        return seg.getB().equals(getA());
    }

    public boolean canBeReplacedWith(EdgeSegment seg) {
        return (!a.isFixed() || a.getPoint().equals(seg.getA().getPoint()))
                && (!b.isFixed() || b.getPoint().equals(seg.getB().getPoint()));
    }

    /**
     * This method usually doesn't change {@code newSegment}, but there is one situation
     * when it does: If a point (a or b) of {@code this} is fixed and the corresponding
     * point of {@code newSegment} isn't, then the {@code newSegment}'s point also becomes fixed.
     */
    public void replaceWith(EdgeSegment newSegment) {
        //    logger.debug("Replace segment " + System.identityHashCode(this) + " with " + System.identityHashCode(newSegment));
        if (!canBeReplacedWith(newSegment)) {
            throw new IllegalArgumentException(this + " cannot be replaced with " + newSegment);
        }
        if (a.isFixed() && !newSegment.getA().isFixed()) {
            assert (a.getPoint().equals(newSegment.getA().getPoint()));
            newSegment.setA(a);
        }
        if (b.isFixed() && !newSegment.getB().isFixed()) {
            assert (b.getPoint().equals(newSegment.getB().getPoint()));
            newSegment.setB(b);
        }
        EdgeSegment oldSegment = this;
        // replace segment in all parent edges
        for (SegmentedEdge se : parents) {
            //      logger.debug(" >> Replace segment " + System.identityHashCode(this) + " with " + System.identityHashCode(newSegment) +
            //          " in edge " + System.identityHashCode(se));
            //      se.replaceSegment(this, newSegment);

            int index = se.indexOf(oldSegment);
            if (index < 0) {
                throw new IllegalArgumentException();
            }
            se.setSegment(index, newSegment);
            newSegment.addParent(se);
        }

        // update prev and next in all parent edges
        for (SegmentedEdge se : parents) {
            int index = se.indexOf(newSegment);

            // update adjacent segments
            // prev
            EdgeSegment prev = se.getPrev(index);
            if (prev != null) {
                prev.setB(newSegment.getA());
            }
            // next
            EdgeSegment next = se.getNext(index);
            if (next != null) {
                next.setA(newSegment.getB());
            }

        }
    }

    //  public List<EdgeSegment> getLeftAdjacentSegments() {
    //    return ImmutableList.copyOf(Iterables.transform(parents, new Function<SegmentedEdge, EdgeSegment>() {
    //      // TODO: remove nulls from the list of adjacent segments
    //      @Override
    //      public EdgeSegment apply(SegmentedEdge se) {
    //        return se.getLeftAdjacent(EdgeSegment.this);
    //      }
    //    }));
    //  }
    //
    //  public List<EdgeSegment> getRightAdjacentSegments() {
    //    return ImmutableList.copyOf(Iterables.transform(parents, new Function<SegmentedEdge, EdgeSegment>() {
    //      @Override
    //      public EdgeSegment apply(SegmentedEdge se) {
    //        return se.getRightAdjacent(EdgeSegment.this);
    //      }
    //    }));
    //  }

    public boolean sharesAParentWith(EdgeSegment other) {
        List<SegmentedEdge> otherParents = other.getParents();
        for (SegmentedEdge parent : getParents()) {
            if (otherParents.contains(parent)) {
                return true;
            }
        }
        return false;
    }

    public double length() {
        return a.distanceTo(b);
    }

    /**
     * Vector dot product
     */
    public double dot(EdgeSegment other) {
        return (b.x() - a.x()) * (other.b.x() - other.a.x()) + (b.y() - a.y()) * (other.b.y() - other.a.y());
    }

    public static double cosOfAngleBetween(EdgeSegment seg1, EdgeSegment seg2) {
        return (seg1.dot(seg2) / seg1.length()) / seg2.length();
    }

    private static final double COS_PI_4 = Math.cos(Math.PI / 4);

    public boolean canBeAggregatedWith(EdgeSegment other) {
        return
        // segments with a fixed point mustn't be aggregated with anything else
        // because it would mean that in the visualization there would be flows
        // (between fixed points) which didn't exist before
        !a.isFixed() && !other.a.isFixed() && !b.isFixed() && !other.b.isFixed() &&

                canBeAggregatedWith_checkLengths(this, other) &&

                // angle is less or equal to PI/4 = 45 grad
                cosOfAngleBetween(this, other) >= COS_PI_4 &&

                // segments of the same edge mustn't be aggregated as well
                !sharesAParentWith(other);
    }

    private static boolean canBeAggregatedWith_checkLengths(EdgeSegment seg1, EdgeSegment seg2) {
        double l1 = seg1.length();
        double l2 = seg2.length();

        return
        // zero-length segments are irrelevant
        (l1 != 0 && l2 != 0) &&

        // length difference shouldn't be too big
                (l1 <= 2 * l2 && l2 <= 2 * l1);
    }

    public EdgeSegment aggregateWith(EdgeSegment other) {
        //    return aggregate(Arrays.asList(this, other));
        if (!canBeAggregatedWith(other)) {
            throw new IllegalArgumentException("Segments cannot be aggregated");
        }

        double alpha = other.weight / (this.weight + other.weight);
        return new EdgeSegment(aggregate(a, other.a, alpha), aggregate(b, other.b, alpha), weight + other.weight,
                //        Iterables.concat(getParents(), other.getParents())
                getParentsOf(this, other));
    }

    /**
     * @param alpha Whether the aggregated point must be closer to p1 or to p2. {@code alpha} must be between 0.0 and 1.0.
     */
    private FPoint aggregate(FPoint p1, FPoint p2, double alpha) {
        if (alpha < 0 || alpha > 1.0) {
            throw new IllegalArgumentException("Alpha must be between 0.0 and 1.0");
        }
        if (p1.isFixed() && p2.isFixed() && !p1.equals(p2)) {
            throw new IllegalArgumentException("Both points are fixed; cannot aggregate");
        }
        if (p1.isFixed()) {
            return p1;
        } else if (p2.isFixed()) {
            return p2;
        } else {
            return new FPoint(GeomUtils.between(p1.getPoint(), p2.getPoint(), alpha), false);
        }
    }

    private static List<SegmentedEdge> getParentsOf(EdgeSegment... segs) {
        List<SegmentedEdge> union = Lists.newArrayList();
        for (EdgeSegment seg : segs) {
            for (SegmentedEdge se : seg.getParents()) {
                if (!union.contains(se)) {
                    union.add(se);
                }
            }
        }
        return union;
    }

    public boolean checkParentEdgesSegmentConsecutivity() {
        for (SegmentedEdge se : getParents()) {
            if (!se.checkSegmentConsecutivity()) {
                return false;
            }
        }
        return true;
    }

    //  public static EdgeSegment aggregate(List<EdgeSegment> segments) {
    //    double sumWeight = 0;
    //    for (EdgeSegment seg : segments) {
    //      sumWeight += seg.getWeight();
    //    }
    //    SPoint newA = null;
    //    SPoint newB = null;
    //    boolean aFixed = false;
    //    boolean bFixed = false;
    //    for (EdgeSegment seg : segments) {
    //      if (seg.isaFixed()) {
    //        if (newA != null) {
    //          throw new IllegalArgumentException("More than one segments have a fixed A point");
    //        }
    //        newA = seg.getA();
    //        aFixed = true;
    //      }
    //      if (seg.isbFixed()) {
    //        if (newB != null) {
    //          throw new IllegalArgumentException("More than one segments have a fixed B point");
    //        }
    //        newB = seg.getB();
    //        bFixed = true;
    //      }
    //    }
    //    if (newA == null) {
    //      newA = GeomUtils.centroid(Iterators.transform(segments.iterator(), EdgeSegment.TRANSFORM_TO_A));
    //    }
    //    if (newB == null) {
    //      newB = GeomUtils.centroid(Iterators.transform(segments.iterator(), EdgeSegment.TRANSFORM_TO_B));
    //    }
    //    return new EdgeSegment(
    //        newA,
    //        aFixed,
    //        newB,
    //        bFixed,
    //        sumWeight,
    //        Iterables.concat(Iterables.transform(segments, TRANSFORM_TO_PARENTS))
    //    );
    //  }

    public static final Function<EdgeSegment, FPoint> TRANSFORM_TO_A = new Function<EdgeSegment, FPoint>() {
        @Override
        public FPoint apply(EdgeSegment segment) {
            return segment.getA();
        }
    };

    public static final Function<EdgeSegment, FPoint> TRANSFORM_TO_B = new Function<EdgeSegment, FPoint>() {
        @Override
        public FPoint apply(EdgeSegment segment) {
            return segment.getB();
        }
    };

    public static final Function<EdgeSegment, List<SegmentedEdge>> TRANSFORM_TO_PARENTS = new Function<EdgeSegment, List<SegmentedEdge>>() {
        @Override
        public List<SegmentedEdge> apply(EdgeSegment segment) {
            return segment.getParents();
        }
    };

    public static final Function<EdgeSegment, Double> TRANSFORM_TO_WEIGHT = new Function<EdgeSegment, Double>() {
        public Double apply(EdgeSegment seg) {
            return seg.getWeight();
        }
    };

    @Override
    public String toString() {
        return "EdgeSegment [" + "a=" + a + ", b=" + b + ", parents.size=" + parents.size() + ", weight=" + weight
                + "]";
    }

}