All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.algart.math.rectangles.IRectanglesUnion Maven / Gradle / Ivy

Go to download

Open-source Java libraries, supporting generalized smart arrays and matrices with elements of any types, including a wide set of 2D-, 3D- and multidimensional image processing and other algorithms, working with arrays and matrices.

There is a newer version: 1.4.23
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.algart.math.rectangles;

import net.algart.math.IPoint;
import net.algart.math.IRectangularArea;

import java.util.*;

/**
 * 

Set-theoretic union of several 2-dimensional {@link IRectangularArea rectangles} * with integer coordinates of vertices and sides, parallel to coordinate axes. * This class allows to solve the following main tasks: *

    *
  1. find connected components in this union;
  2. *
  3. find its boundary as a polygon: a sequence of links, where each link is a horizontal or vertical * segment (1st link is horizontal, 2nd is vertical, 3rd is horizontal, etc.);
  4. *
  5. find the largest rectangle (with sides, parallel to the coordinate axes), which is a subset of this union.
  6. *
* *

This class is immutable and thread-safe: * there are no ways to modify settings of the created instance. * However, all important information is returned "lazily", i.e. while the 1st attempt to read it. * So, the instance creation method {@link #newInstance(Collection)} * works quickly and does not lead to complex calculations and allocating additional memory.

* * @author Daniel Alievsky */ public class IRectanglesUnion { static final int DEBUG_LEVEL = net.algart.arrays.Arrays.SystemSettings.getIntProperty( "net.algart.math.rectangles.debugLevel", 0); private static final boolean USE_SECOND_SIDES_WHILE_SEARCHING_CONNECTIONS = false; // - it seems that we can prove that this can be false always public static class Frame { final HorizontalSide lessHorizontalSide; final HorizontalSide higherHorizontalSide; final VerticalSide lessVerticalSide; final VerticalSide higherVerticalSide; private final IRectangularArea rectangle; private final long fromX; private final long toX; private final long fromY; private final long toY; final int index; private Frame(IRectangularArea rectangle, int index) { assert rectangle != null; this.rectangle = rectangle; this.lessHorizontalSide = new HorizontalSide(true, this); this.higherHorizontalSide = new HorizontalSide(false, this); this.lessVerticalSide = new VerticalSide(true, this); this.higherVerticalSide = new VerticalSide(false, this); this.fromX = rectangle.min(0); this.toX = rectangle.max(0) + 1; this.fromY = rectangle.min(1); this.toY = rectangle.max(1) + 1; this.index = index; } public IRectangularArea rectangle() { return rectangle; } @Override public String toString() { return "Frame #" + index + " (" + rectangle + ")"; } } public static abstract class Side implements Comparable { final boolean first; private Side(boolean first) { this.first = first; } public boolean isFirstOfTwoParallelSides() { return first; } public boolean isSecondOfTwoParallelSides() { return !first; } public abstract boolean isHorizontal(); public long frameSideCoord() { return isFirstOfTwoParallelSides() ? coord() : coord() - 1; } /** * Returns the coordinate of this frame side along the coordinate axis, * to which this side is perpendicular, increased by 0.5 * (the sides always have half-integer coordinates). * * @return the perpendicular coordinate of this side + 0.5 */ public abstract long coord(); /** * Returns the starting coordinate of this frame side along the coordinate axis, * to which this link is parallel, increased by 0.5 * (the sides always have half-integer coordinates). * * @return the starting coordinate of this side + 0.5 */ public abstract long from(); /** * Returns the ending coordinate of this frame side along the coordinate axis, * to which this link is parallel, increased by 0.5 * (the sides always have half-integer coordinates). * * @return the ending coordinate of this side + 0.5 */ public abstract long to(); public IRectangularArea equivalentRectangle() { final long coord = frameSideCoord(); final long from = from(); final long to = to(); assert from <= to; if (from == to) { return null; } else if (isHorizontal()) { return IRectangularArea.valueOf(from, coord, to - 1, coord); } else { return IRectangularArea.valueOf(coord, from, coord, to - 1); } } @Override public int compareTo(Side o) { if (this.getClass() != o.getClass()) { throw new ClassCastException("Comparison of sides with different types: " + getClass() + " != " + o.getClass()); } final long thisCoord = this.coord(); final long otherCoord = o.coord(); if (thisCoord < otherCoord) { return -1; } if (thisCoord > otherCoord) { return 1; } // Lets we have two adjacent rectangles: // AAAAAAAAAAAA // AAAAAAAAAAAA // AAAAAAAAAAAA // BBBBBBBBBBB // BBBBBBBBBBB // BBBBBBBBBBB // where the top side of B lies at the same horizontal as the bottom side of A. // The following checks provide the sorting order, when "opening" top side of B // will be BEFORE the "closing" bottom side of A: // A top side (A start) // B top side (B start) // A bottom side (A finish) // B bottom side (B finish) // It is important to consider such rectangles intersecting. if (this.first && !o.first) { return -1; } if (!this.first && o.first) { return 1; } // Sorting along another coordinate is necessary for searching several sides, // which are really a single continuous segment final long thisFrom = this.from(); final long otherFrom = o.from(); if (thisFrom < otherFrom) { return -1; } if (thisFrom > otherFrom) { return 1; } final long thisTo = this.to(); final long otherTo = o.to(); if (thisTo < otherTo) { return -1; } if (thisTo > otherTo) { return 1; } return 0; } @Override public String toString() { return (isHorizontal() ? (first ? "top" : "bottom") : (first ? "left" : "right")) + " side" + (this instanceof FrameSide ? " of frame #" + ((FrameSide) this).frame.index : "") + ": " + from() + ".." + to() + " at " + coord(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Side side = (Side) o; if (first != side.first) { return false; } return this.coord() == side.coord() && this.from() == side.from() && this.to() == side.to(); } @Override public int hashCode() { int result = (first ? 1 : 0); final long coord = coord(); final long from = from(); final long to = to(); result = 31 * result + (int) (coord ^ (coord >>> 32)); result = 31 * result + (int) (from ^ (from >>> 32)); result = 31 * result + (int) (to ^ (to >>> 32)); return result; } abstract void allContainedFrameSides(List result); abstract FrameSide transversalFrameSideFrom(); abstract FrameSide transversalFrameSideTo(); } public static abstract class FrameSide extends Side { final Frame frame; SideSeries parentSeries = null; private FrameSide(boolean first, Frame frame) { super(first); assert frame != null; this.frame = frame; } public Frame frame() { return frame; } @Override void allContainedFrameSides(List result) { result.clear(); result.add(this); } } public static class HorizontalSide extends FrameSide { private HorizontalSide(boolean first, Frame frame) { super(first, frame); } @Override public boolean isHorizontal() { return true; } @Override public long frameSideCoord() { return first ? frame.fromY : frame.toY - 1; } @Override public long coord() { return first ? frame.fromY : frame.toY; } @Override public long from() { return frame.fromX; } @Override public long to() { return frame.toX; } @Override FrameSide transversalFrameSideFrom() { return frame.lessVerticalSide; } @Override FrameSide transversalFrameSideTo() { return frame.higherVerticalSide; } } public static class VerticalSide extends FrameSide { private VerticalSide(boolean first, Frame frame) { super(first, frame); } @Override public boolean isHorizontal() { return false; } @Override public long frameSideCoord() { return first ? frame.fromX : frame.toX - 1; } @Override public long coord() { return first ? frame.fromX : frame.toX; } @Override public long from() { return frame.fromY; } @Override public long to() { return frame.toY; } @Override FrameSide transversalFrameSideFrom() { return frame.lessVerticalSide; } @Override FrameSide transversalFrameSideTo() { return frame.higherVerticalSide; } } public static abstract class BoundaryLink implements Comparable { final SideSeries parentSeries; long from; long to; int indexInSortedList = -1; boolean joinedIntoAllBoundaries = false; private BoundaryLink( SideSeries parentSeries, long from, long to) { assert parentSeries != null; this.parentSeries = parentSeries; assert from <= to; this.from = from; this.to = to; } public boolean atFirstOfTwoParallelSides() { return parentSeries.first; } public boolean atSecondOfTwoParallelSides() { return !parentSeries.first; } public abstract boolean isHorizontal(); /** * Returns the coordinate of this boundary element (link) along the coordinate axis, * to which this link is perpendicular, increased by 0.5 * (the bounrady always has half-integer coordinate). * * @return the perpendicular coordinate of this link + 0.5 */ public long coord() { return parentSeries.coord(); } /** * Returns the starting coordinate of this boundary element (link) along the coordinate axis, * to which this link is parallel, increased by 0.5 * (the bounrady always has half-integer coordinate). * * @return the starting coordinate of this link + 0.5 */ public long from() { return from; } /** * Returns the ending coordinate of this boundary element (link) along the coordinate axis, * to which this link is parallel, increased by 0.5 * (the bounrady always has half-integer coordinate). * * @return the ending coordinate of this link + 0.5 */ public long to() { return to; } public abstract BoundaryLink linkFrom(); public abstract BoundaryLink linkTo(); public abstract IRectangularArea equivalentRectangle(); @Override public int compareTo(BoundaryLink o) { if (this.coord() < o.coord()) { return -1; } if (this.coord() > o.coord()) { return 1; } if (this.from < o.from) { return -1; } if (this.from > o.from) { return 1; } if (this.to < o.to) { return -1; } if (this.to > o.to) { return 1; } return this.parentSeries.compareTo(o.parentSeries); } @Override public String toString() { return (this instanceof HorizontalBoundaryLink ? "horizontal" : "vertical") + " boundary link " + from + ".." + to + " at " + parentSeries; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } BoundaryLink that = (BoundaryLink) o; if (from != that.from) { return false; } if (to != that.to) { return false; } return this.parentSeries.equals(that.parentSeries); } @Override public int hashCode() { int result = parentSeries.hashCode(); result = 31 * result + (int) (from ^ (from >>> 32)); result = 31 * result + (int) (to ^ (to >>> 32)); return result; } double areaUnderLink() { final double area = ((double) to - (double) from) * (double) coord(); return atFirstOfTwoParallelSides() ? -area : area; } } public static class HorizontalBoundaryLink extends BoundaryLink { final VerticalSideSeries transversalSeriesFrom; final VerticalSideSeries transversalSeriesTo; // - these two fields are necessary only while constructing the boundary VerticalBoundaryLink linkFrom = null; VerticalBoundaryLink linkTo = null; private HorizontalBoundaryLink( HorizontalSideSeries parentSeries, VerticalSideSeries transversalSeriesFrom, VerticalSideSeries transversalSeriesTo) { super(parentSeries, transversalSeriesFrom.coord(), transversalSeriesTo.coord()); this.transversalSeriesFrom = transversalSeriesFrom; this.transversalSeriesTo = transversalSeriesTo; } @Override public boolean isHorizontal() { return true; } @Override public VerticalBoundaryLink linkFrom() { return linkFrom; } @Override public VerticalBoundaryLink linkTo() { return linkTo; } @Override public IRectangularArea equivalentRectangle() { return IRectangularArea.valueOf( from, parentSeries.frameSideCoord(), to - 1, parentSeries.frameSideCoord()); } void setNeighbour(VerticalBoundaryLink neighbour) { if (neighbour.parentSeries == transversalSeriesFrom) { linkFrom = neighbour; } else if (neighbour.parentSeries == transversalSeriesTo) { linkTo = neighbour; } else { throw new AssertionError("Attempt to assing vertical neighbour from alien side series"); } } } public static class VerticalBoundaryLink extends BoundaryLink { final HorizontalBoundaryLink linkFrom; final HorizontalBoundaryLink linkTo; private VerticalBoundaryLink( VerticalSideSeries parentSeries, HorizontalBoundaryLink linkFrom, HorizontalBoundaryLink linkTo) { super(parentSeries, linkFrom.coord(), linkTo.coord()); this.linkFrom = linkFrom; this.linkTo = linkTo; } @Override public boolean isHorizontal() { return false; } @Override public HorizontalBoundaryLink linkFrom() { return linkFrom; } @Override public HorizontalBoundaryLink linkTo() { return linkTo; } @Override public IRectangularArea equivalentRectangle() { return IRectangularArea.valueOf( parentSeries.frameSideCoord(), from, parentSeries.frameSideCoord(), to - 1); } } private final List frames; private final IRectangularArea circumscribedRectangle; private List horizontalSides = null; private List verticalSides = null; private List> connectedComponents = null; private List horizontalSideSeries = null; private List verticalSideSeries = null; private List horizontalSideSeriesAtBoundary = null; private List verticalSideSeriesAtBoundary = null; private long[] allDifferentXAtBoundary = null; private double unionArea = Double.NaN; private List> allBoundaries = null; private List horizontalSectionsByLowerSides = null; // - this field is accessed via reflection in the test private IRectangularArea largestRectangleInUnion = null; private final Object lock = new Object(); IRectanglesUnion(List frames) { this.frames = frames; if (frames.isEmpty()) { circumscribedRectangle = null; } else { long minX = Long.MAX_VALUE; long minY = Long.MAX_VALUE; long maxX = Long.MIN_VALUE; long maxY = Long.MIN_VALUE; for (Frame frame : frames) { minX = Math.min(minX, frame.fromX); minY = Math.min(minY, frame.fromY); maxX = Math.max(maxX, frame.toX - 1); maxY = Math.max(maxY, frame.toY - 1); } circumscribedRectangle = IRectangularArea.valueOf(minX, minY, maxX, maxY); } } public static IRectanglesUnion newInstance(Collection rectangles) { return new IRectanglesUnion(checkAndConvertToFrames(rectangles)); } /** * Subtracts the given rectangle (via {@link IRectangularArea#subtractCollection(Queue, Collection)} method) * from the set of rectangles, containing in this object, and return the union of the resulting rectangles. * * @param whatToSubtract the subtracted rectangle. * @return the union of the rest of rectangles after subtraction. * @throws NullPointerException if the argument is {@code null}. * @throws IllegalArgumentException if whatToSubtract.coordCount() != 2. */ public IRectanglesUnion subtractRectangle(IRectangularArea whatToSubtract) { Objects.requireNonNull(whatToSubtract, "Null rectangle"); if (whatToSubtract.coordCount() != 2) { throw new IllegalArgumentException("Only 2-dimensional rectangle can be subtracted"); } Queue rectangles = new LinkedList<>(); for (Frame frame : frames) { rectangles.add(frame.rectangle); } IRectangularArea.subtractCollection(rectangles, whatToSubtract); return newInstance(rectangles); } public IRectanglesUnion subtractLargestRectangle() { if (frames.isEmpty()) { return this; } return subtractRectangle(largestRectangleInUnion()); } public List frames() { return Collections.unmodifiableList(frames); } public IRectangularArea circumscribedRectangle() { return circumscribedRectangle; } public List horizontalSides() { synchronized (lock) { return Collections.unmodifiableList(horizontalSides); } } public List verticalSides() { synchronized (lock) { return Collections.unmodifiableList(verticalSides); } } public int connectedComponentCount() { synchronized (lock) { findConnectedComponents(); return connectedComponents.size(); } } public IRectanglesUnion connectedComponent(int index) { synchronized (lock) { findConnectedComponents(); final List resultFrames = cloneFrames(connectedComponents.get(index)); final IRectanglesUnion result = new IRectanglesUnion(resultFrames); result.connectedComponents = Collections.singletonList(resultFrames); return result; } } public List allHorizontalBoundaryLinks() { synchronized (lock) { findBoundaries(); final List result = new ArrayList<>(); for (HorizontalSideSeries series : horizontalSideSeriesAtBoundary) { result.addAll(series.containedBoundaryLinks); } return result; } } public List allVerticalBoundaryLinks() { synchronized (lock) { findBoundaries(); final List result = new ArrayList<>(); for (VerticalSideSeries series : verticalSideSeriesAtBoundary) { result.addAll(series.containedBoundaryLinks); } return result; } } // First boundary in the result is the external contour. public List> allBoundaries() { synchronized (lock) { findBoundaries(); return Collections.unmodifiableList(allBoundaries); } } public double unionArea() { synchronized (lock) { findBoundaries(); return unionArea; } } public IRectangularArea largestRectangleInUnion() { synchronized (lock) { findLargestRectangleInUnion(); return largestRectangleInUnion; } } /** * Forces this object to find all connected components. * It does not affect to results of any other methods, but after this call the following methods * will work quickly: *
    *
  • {@link #connectedComponentCount()}
  • *
  • {@link #connectedComponent(int)}
  • *
  • {@link #horizontalSides()}
  • *
  • {@link #verticalSides()}
  • *
*/ public void findConnectedComponents() { synchronized (lock) { doCreateSideLists(); if (this.connectedComponents != null) { return; } if (frames.isEmpty()) { this.connectedComponents = Collections.emptyList(); return; } long t1 = System.nanoTime(); final List> connectionLists = createListOfLists(frames.size()); long t2 = System.nanoTime(); final long nConnections = doFillConnectionLists(connectionLists); long t3 = System.nanoTime(); final List> result = doFindConnectedComponents(connectionLists); long t4 = System.nanoTime(); this.connectedComponents = result; debug(1, "Rectangle union (%d rectangles), finding %d connected components: " + "%.3f ms = %.3f ms initializing + %.3f finding %d connections + %.3f breadth-first search " + "(%.3f mcs / rectangle)%n", frames.size(), result.size(), (t4 - t1) * 1e-6, (t2 - t1) * 1e-6, (t3 - t2) * 1e-6, nConnections, (t4 - t3) * 1e-6, (t4 - t1) * 1e-3 / (double) frames.size()); if (DEBUG_LEVEL >= 2 && result.size() >= 2) { t1 = System.nanoTime(); for (int i = 0; i < result.size(); i++) { for (Frame frame1 : result.get(i)) { for (int j = i + 1; j < result.size(); j++) { for (Frame frame2 : result.get(j)) { if (frame1.rectangle.intersects(frame2.rectangle)) { // Note: this check is even more strong than requirement of this class // (attached rectangles are considered to be in the single component) throw new AssertionError("First 2 connected component really have intersection: " + frame1 + " (component " + i + ") intersects " + frame2 + " (component " + j + ")"); } } } } } t2 = System.nanoTime(); debug(2, "Testing connected components: %.3f ms%n", (t2 - t1) * 1e-6); } } } /** * Forces this object to find the boundary of this union of rectangles. * It does not affect to results of any other methods, but after this call the following methods * will work quickly: *
    *
  • {@link #allHorizontalBoundaryLinks()}
  • *
  • {@link #allVerticalBoundaryLinks()}
  • *
  • {@link #allBoundaries()}
  • *
  • {@link #horizontalSides()}
  • *
  • {@link #verticalSides()}
  • *
*/ public void findBoundaries() { synchronized (lock) { doCreateSideLists(); // Global synchronization necessary, because we change internal fields like parentSeries // in already existing structures. if (this.allBoundaries != null) { return; } if (frames.isEmpty()) { this.horizontalSideSeries = Collections.emptyList(); this.verticalSideSeries = Collections.emptyList(); this.horizontalSideSeriesAtBoundary = Collections.emptyList(); this.verticalSideSeriesAtBoundary = Collections.emptyList(); this.allDifferentXAtBoundary = new long[0]; this.unionArea = 0.0; this.allBoundaries = Collections.emptyList(); return; } long t1 = System.nanoTime(); this.horizontalSideSeries = createHorizontalSideSeriesLists(); this.verticalSideSeries = createVerticalSideSeriesLists(); long t2 = System.nanoTime(); final long hCount = doFindHorizontalBoundaries(); long t3 = System.nanoTime(); this.horizontalSideSeriesAtBoundary = new ArrayList<>(); doExtractHorizontalSeriesAtBoundary(); long t4 = System.nanoTime(); final long vCount = doConvertHorizontalToVerticalLinks(); this.verticalSideSeriesAtBoundary = new ArrayList<>(); long t5 = System.nanoTime(); doExtractVerticalSeriesAtBoundary(); if (vCount != hCount) { throw new AssertionError("Different numbers of horizontal and vertical links found"); } long t6 = System.nanoTime(); this.allDifferentXAtBoundary = doExtractAllDifferentXAtBoundary(); this.unionArea = doCalculateArea(); doSetLinkIndexes(hCount); assert hCount <= Integer.MAX_VALUE; // - it was checked in doSetLinkIndexes() this.allBoundaries = doJoinBoundaries(hCount); long t7 = System.nanoTime(); if (DEBUG_LEVEL >= 1) { long totalLinkCount = totalCount(allBoundaries); debug(1, "Rectangle union (%d rectangles), area %.1f, finding %d boundaries with %d links: " + "%.3f ms = %.3f ms initializing " + "+ %.3f ms %d horizontal links " + "+ %.3f ms %d/%d horizontals at boundary " + "+ %.3f ms %d vertical links " + "+ %.3f ms %d/%d verticals at boundary " + "+ %.3f ms postprocessing and joining links " + "(%.3f mcs / rectangle, %.3f mcs / link)%n", frames.size(), unionArea, allBoundaries().size(), totalLinkCount, (t7 - t1) * 1e-6, (t2 - t1) * 1e-6, (t3 - t2) * 1e-6, hCount, (t4 - t3) * 1e-6, horizontalSideSeriesAtBoundary.size(), horizontalSideSeries.size(), (t5 - t4) * 1e-6, vCount, (t6 - t5) * 1e-6, verticalSideSeriesAtBoundary.size(), verticalSideSeries.size(), (t7 - t6) * 1e-6, (t7 - t1) * 1e-3 / (double) frames.size(), (t7 - t1) * 1e-3 / (double) totalLinkCount); } if (DEBUG_LEVEL >= 2) { final StringBuilder sb = new StringBuilder(); for (int k = 0; k < allDifferentXAtBoundary.length; k++) { if (k == 100) { sb.append("..."); break; } sb.append("(").append(k).append(":)").append(allDifferentXAtBoundary[k]).append(", "); } debug(2, "Found %d verticals with different x: %s%n", allDifferentXAtBoundary.length, sb); } } } /** * Forces this object to find the largest rectangle (with sides, parallel to the coordinate axes), * which is a subset of this union of rectangles. * It does not affect to results of any other methods, but after this call the following methods * will work quickly: *
    *
  • {@link #largestRectangleInUnion()}
  • *
  • {@link #allHorizontalBoundaryLinks()}
  • *
  • {@link #allVerticalBoundaryLinks()}
  • *
  • {@link #allBoundaries()}
  • *
  • {@link #horizontalSides()}
  • *
  • {@link #verticalSides()}
  • *
*/ public void findLargestRectangleInUnion() { synchronized (lock) { findBoundaries(); if (this.largestRectangleInUnion != null) { return; } if (frames.isEmpty()) { this.horizontalSectionsByLowerSides = Collections.emptyList(); this.largestRectangleInUnion = null; return; } long t1 = System.nanoTime(); final List horizontalSectionsByLowerSides = doFindHorizontalSections(); long t2 = System.nanoTime(); final List> closingLinksIntersectingEachVertical = doFindClosingLinksIntersectingEachVertical(); long t3 = System.nanoTime(); final SearchIRectangleInHypograph searcher = doFindLargestRegtangles( horizontalSectionsByLowerSides, closingLinksIntersectingEachVertical); long t4 = System.nanoTime(); this.horizontalSectionsByLowerSides = horizontalSectionsByLowerSides; this.largestRectangleInUnion = searcher.largestRectangle(); if (DEBUG_LEVEL >= 1) { long totalLinkCount = totalCount(allBoundaries); debug(1, "Rectangle union (%d rectangles, %d links), " + "finding largest rectangle %s (area %.1f): " + "%.3f ms = %.3f ms %d horizontal sections " + "+ %.3f ms %d links " + "intersecting with %d verticals " + "+ %.3f ms searching largest rectangle " + "(%.3f mcs / rectangle, %.3f mcs / link)%n", frames.size(), totalLinkCount, largestRectangleInUnion, largestRectangleInUnion.volume(), (t4 - t1) * 1e-6, (t2 - t1) * 1e-6, horizontalSectionsByLowerSides.size(), (t3 - t2) * 1e-6, totalCount(closingLinksIntersectingEachVertical), allDifferentXAtBoundary.length - 1, (t4 - t3) * 1e-6, (t4 - t1) * 1e-3 / (double) frames.size(), (t4 - t1) * 1e-3 / (double) totalLinkCount); } if (DEBUG_LEVEL >= 2) { t1 = System.nanoTime(); for (HorizontalSection section : horizontalSectionsByLowerSides) { IRectangularArea r = section.equivalentRectangle(); Queue queue = new LinkedList<>(); queue.add(r); for (Frame frame : frames) { IRectangularArea.subtractCollection(queue, IRectangularArea.valueOf(frame.fromX, frame.fromY, frame.toX - 1, frame.toY)); // Note: we need to use toY instead of toY-1, because the section lies BETWEEN pixels } if (!queue.isEmpty()) { throw new AssertionError("Section " + section + " is not a subset of the union"); } } t2 = System.nanoTime(); debug(2, "Testing horizontal sections: %.3f ms%n", (t2 - t1) * 1e-6); } } } @Override public String toString() { synchronized (lock) { return "union of " + frames.size() + " rectangles" + (connectedComponents == null ? "" : ", " + connectedComponents.size() + " connected components"); } } public static double areaInBoundary(List boundary) { Objects.requireNonNull(boundary, "Null boundary"); double result = 0.0; for (BoundaryLink link : boundary) { if (link.isHorizontal()) { result += link.areaUnderLink(); } } return result; } public static List boundaryVerticesPlusHalf(List boundary) { Objects.requireNonNull(boundary, "Null boundary"); final List result = new ArrayList<>(); BoundaryLink last = null; for (BoundaryLink link : boundary) { final long coord = link.coord(); final long secondCoord = last == link.linkTo() ? link.to : link.from; // Note: in boundaries, built by this class, last.isHorizontal() != link.isHorizontal(), // but this may be not so in third-party boundaries. result.add(link.isHorizontal() ? IPoint.valueOf(secondCoord, coord) : IPoint.valueOf(coord, secondCoord)); last = link; } if (DEBUG_LEVEL >= 3) { debug(3, "Boundary precise vertices +0.5: %s%n", result); } return result; } public static List boundaryVerticesAtRectangles(List boundary) { Objects.requireNonNull(boundary, "Null boundary"); final List result = new ArrayList<>(); final int n = boundary.size(); if (n == 0) { return result; } BoundaryLink last = boundary.get(n - 1); for (BoundaryLink link : boundary) { final boolean lastFirst = last.atFirstOfTwoParallelSides(); final boolean thisFirst = link.atFirstOfTwoParallelSides(); // Note: in boundaries, built by this class, last.isHorizontal() != link.isHorizontal(), // but this may be not so in third-party boundaries. final long coord = thisFirst ? link.coord() : link.coord() - 1; final long secondCoord = last == link.linkTo() ? lastFirst ? link.to : link.to - 1 : lastFirst ? link.from : link.from - 1; result.add(link.isHorizontal() ? IPoint.valueOf(secondCoord, coord) : IPoint.valueOf(coord, secondCoord)); last = link; } if (DEBUG_LEVEL >= 1) { int k = 0; for (BoundaryLink link : boundary) { final IPoint v1 = result.get(k); final IPoint v2 = result.get(k == n - 1 ? 0 : k + 1); final IRectangularArea onVertices = IRectangularArea.valueOf(v1.min(v2), v1.max(v2)); final IRectangularArea onLink = link.equivalentRectangle(); if (!onVertices.contains(onLink)) { throw new AssertionError("Boundary rectangle does not contain the link #" + k + ": " + v1 + ", " + v2 + ", " + onLink); } if (onVertices.volume() - onLink.volume() > 2.0001) { throw new AssertionError("Boundary rectangle is too large: link #" + k + ": " + v1 + ", " + v2 + ", " + onLink); } k++; } } if (DEBUG_LEVEL >= 3) { debug(3, "Boundary vertices at rectangles: %s%n", result); } return result; } private void doCreateSideLists() { if (this.horizontalSides != null) { return; } long t1 = System.nanoTime(); final List horizontalSides = new ArrayList<>(); final List verticalSides = new ArrayList<>(); for (Frame frame : frames) { horizontalSides.add(frame.lessHorizontalSide); horizontalSides.add(frame.higherHorizontalSide); verticalSides.add(frame.lessVerticalSide); verticalSides.add(frame.higherVerticalSide); } Collections.sort(horizontalSides); Collections.sort(verticalSides); long t2 = System.nanoTime(); this.horizontalSides = horizontalSides; this.verticalSides = verticalSides; debug(1, "Rectangle union (%d rectangles), allocating and sorting sides: %.3f ms%n", frames.size(), (t2 - t1) * 1e-6); } private long doFillConnectionLists(List> connectionLists) { assert !frames.isEmpty(); final HorizontalIBracketSet bracketSet = new HorizontalIBracketSet<>(horizontalSides, false); long count = 0; while (bracketSet.next()) { if (bracketSet.horizontal.first) { final IBracket lastBefore = bracketSet.lastIntersectionBeforeLeft(); if (lastBefore != null && lastBefore.followingCoveringDepth > 0) { // Special case. We can be sure that this previous frame is in the same connected component, // and we need to add a fictive connection with it to correctly process situation, // when some frame or set of frames lies strictly inside another rectangle. // Moreover, this fictive link allows to check only first horizontal sides: // even it fully lies inside another rectangle and does not intersect its vertical sides, // we can reach that rectangle via such fictive limks. addConnection(connectionLists, lastBefore.intersectingSide.frame, bracketSet.horizontal.frame); count++; } } if (USE_SECOND_SIDES_WHILE_SEARCHING_CONNECTIONS || bracketSet.horizontal.first) { for (IBracket bracket : bracketSet.currentIntersections()) { if (DEBUG_LEVEL >= 2) { assert bracket.covers(bracketSet.coord); } addConnection(connectionLists, bracket.intersectingSide.frame, bracketSet.horizontal.frame); count++; } } } return count; } private List> doFindConnectedComponents(List> connectionLists) { assert !frames.isEmpty(); final List> result = new ArrayList<>(); final boolean[] frameVisited = new boolean[frames.size()]; // - filled by false by Java final Queue queue = new LinkedList<>(); int index = 0; for (; ; ) { while (index < frameVisited.length && frameVisited[index]) { index++; } if (index >= frameVisited.length) { break; } final List component = new ArrayList<>(); // Breadth-first search: queue.add(frames.get(index)); frameVisited[index] = true; while (!queue.isEmpty()) { final Frame frame = queue.poll(); component.add(frame); final List neighbours = connectionLists.get(frame.index); for (Frame neighbour : neighbours) { if (!frameVisited[neighbour.index]) { queue.add(neighbour); frameVisited[neighbour.index] = true; } } if (DEBUG_LEVEL >= 4) { debug(4, " Neighbours of %s:%n", frame); for (Frame neighbour : neighbours) { debug(4, " %s%n", neighbour); } } } result.add(component); } return result; } private List createHorizontalSideSeriesLists() { final List result = new ArrayList<>(); HorizontalSideSeries last = null; for (HorizontalSide side : horizontalSides) { boolean expanded = last != null && last.expand(side); if (!expanded) { if (last != null) { result.add(last); } last = new HorizontalSideSeries(side); } } if (last != null) { result.add(last); } checkSidesSeriesList(result); return result; } private List createVerticalSideSeriesLists() { final List result = new ArrayList<>(); VerticalSideSeries last = null; for (VerticalSide side : verticalSides) { boolean expanded = last != null && last.expand(side); if (!expanded) { if (last != null) { result.add(last); } last = new VerticalSideSeries(side); } } if (last != null) { result.add(last); } checkSidesSeriesList(result); return result; } private void checkSidesSeriesList(List sideSeries) { if (DEBUG_LEVEL >= 2) { for (int k = 1, n = sideSeries.size(); k < n; k++) { assert sideSeries.get(k - 1).compareTo(sideSeries.get(k)) <= 0; } } if (DEBUG_LEVEL >= 3) { debug(3, " %d side series:%n", sideSeries.size()); for (int k = 0, n = sideSeries.size(); k < n; k++) { debug(3, " side series %d/%d: %s%n", k, n, sideSeries.get(k)); } } } private long doFindHorizontalBoundaries() { assert !frames.isEmpty(); final HorizontalIBracketSet bracketSet = new HorizontalIBracketSet<>(horizontalSideSeries, true); long count = 0; while (bracketSet.next()) { final Set brackets = bracketSet.currentIntersections(); final IBracket lastBefore = bracketSet.lastIntersectionBeforeLeft(); boolean lastRightAtBoundary = lastBefore == null || lastBefore.followingCoveringDepth == 0; FrameSide lastLeftVertical = lastRightAtBoundary ? bracketSet.horizontal.transversalFrameSideFrom() : null; for (IBracket bracket : brackets) { if (DEBUG_LEVEL >= 2) { assert bracket.covers(bracketSet.coord); } boolean rightAtBoundary = bracket.followingCoveringDepth == 0; if (rightAtBoundary == lastRightAtBoundary) { continue; } if (rightAtBoundary) { lastLeftVertical = bracket.intersectingSide; } else { addHorizontalLink(bracketSet, lastLeftVertical, bracket.intersectingSide); } lastRightAtBoundary = rightAtBoundary; } if (lastRightAtBoundary) { addHorizontalLink(bracketSet, lastLeftVertical, bracketSet.horizontal.transversalFrameSideTo()); } count += bracketSet.horizontal.containedLinksCount(); } return count; } private long doConvertHorizontalToVerticalLinks() { assert !frames.isEmpty(); for (VerticalSideSeries verticalSeries : verticalSideSeries) { assert verticalSeries.containedBoundaryLinks == null : "non-null containedBoundaryLinks = " + verticalSeries.containedBoundaryLinks; assert verticalSeries.intersectingBoundaryLinks == null : "non-null intersectingBoundaryLinks = " + verticalSeries.intersectingBoundaryLinks; } for (HorizontalSideSeries series : horizontalSideSeriesAtBoundary) { for (HorizontalBoundaryLink link : series.containedBoundaryLinks) { link.transversalSeriesFrom.addIntersectingLink(link); link.transversalSeriesTo.addIntersectingLink(link); } } HorizontalBoundaryLink[] horizontalLinks = new HorizontalBoundaryLink[0]; long count = 0; for (VerticalSideSeries verticalSeries : verticalSideSeries) { if (verticalSeries.intersectingBoundaryLinks == null) { // - no links added here by the previous loop continue; } final int horizontalsCount = verticalSeries.intersectingBoundaryLinks.size(); assert horizontalsCount > 0 && horizontalsCount % 2 == 0 : "Invalid horizontalsCount=" + horizontalsCount; horizontalLinks = verticalSeries.intersectingBoundaryLinks.toArray(horizontalLinks); Arrays.sort(horizontalLinks, 0, horizontalsCount); for (int k = 0; k < horizontalsCount; k += 2) { final HorizontalBoundaryLink linkFrom = horizontalLinks[k]; final HorizontalBoundaryLink linkTo = horizontalLinks[k + 1]; final long from = linkFrom.coord(); final long to = linkTo.coord(); assert k == 0 || from > horizontalLinks[k - 1].coord() : "Two horizontal links with the same ordinate " + from + " (=" + horizontalLinks[k - 1].coord() + ") are incident with the same vertical side"; assert from < to : "Empty vertical link #" + (k / 2) + ": " + from + ".." + to; final VerticalBoundaryLink link = new VerticalBoundaryLink(verticalSeries, linkFrom, linkTo); linkFrom.setNeighbour(link); linkTo.setNeighbour(link); verticalSeries.addLink(link); } count += verticalSeries.containedLinksCount(); } return count; } private void doExtractHorizontalSeriesAtBoundary() { int count = 0; for (HorizontalSideSeries series : horizontalSideSeries) { if (series.containsBoundary()) { series.indexInSortedListAtBoundary = count++; this.horizontalSideSeriesAtBoundary.add(series); } } } private void doExtractVerticalSeriesAtBoundary() { int count = 0; for (VerticalSideSeries series : verticalSideSeries) { if (series.containsBoundary()) { series.indexInSortedListAtBoundary = count++; this.verticalSideSeriesAtBoundary.add(series); } } } private long[] doExtractAllDifferentXAtBoundary() { int count = 0; long lastX = 157; for (VerticalSideSeries series : verticalSideSeriesAtBoundary) { assert series.containsBoundary(); final long coord = series.coord(); if (count == 0 || coord != lastX) { lastX = coord; count++; } series.numberOfLessCoordinatesAtBoundary = count - 1; } final long[] result = new long[count]; count = 0; for (VerticalSideSeries series : verticalSideSeriesAtBoundary) { final long coord = series.coord(); if (count == 0 || coord != lastX) { lastX = coord; result[count] = coord; count++; } } return result; } private void doSetLinkIndexes(long horizontalOrVerticalLinksCount) { int count = 0; for (HorizontalSideSeries series : horizontalSideSeriesAtBoundary) { for (HorizontalBoundaryLink link : series.containedBoundaryLinks) { if (count == Integer.MAX_VALUE) { throw new OutOfMemoryError("Number of horizontal links must be < 2^31"); } link.indexInSortedList = count++; } } assert count == horizontalOrVerticalLinksCount; count = 0; for (VerticalSideSeries series : verticalSideSeriesAtBoundary) { for (VerticalBoundaryLink link : series.containedBoundaryLinks) { if (count == Integer.MAX_VALUE) { throw new OutOfMemoryError("Number of vertical links must be < 2^31"); } link.indexInSortedList = count++; } } assert count == horizontalOrVerticalLinksCount; } private double doCalculateArea() { double result = 0.0; for (HorizontalSideSeries series : horizontalSideSeriesAtBoundary) { for (HorizontalBoundaryLink link : series.containedBoundaryLinks) { result += link.areaUnderLink(); } } return result; } private List> doJoinBoundaries(long numberOfHorizontalLinks) { assert !frames.isEmpty(); final long maxCount = 10 * Math.min(numberOfHorizontalLinks, Integer.MAX_VALUE); // really the limit is 2 * numberOfHorizontalLinks; // Integer.MAX_VALUE is also the limit due to 31-bit result.size() List> result = new ArrayList<>(); for (HorizontalSideSeries series : horizontalSideSeriesAtBoundary) { for (HorizontalBoundaryLink link : series.containedBoundaryLinks) { if (!link.joinedIntoAllBoundaries) { final List boundary = scanBoundary(link, maxCount); result.add(Collections.unmodifiableList(boundary)); } } } return result; } // This method is saved for debugging needs private List doFindHorizontalSectionsSlowly() { assert !frames.isEmpty(); final List result = new ArrayList<>(); final HorizontalIBracketSet bracketSet = new HorizontalIBracketSet<>(horizontalSideSeries, false); HorizontalSection lastSection = null; while (bracketSet.next()) { if (bracketSet.horizontal.first && bracketSet.horizontal.containsBoundary()) { // We should remember that the current section of the whole figure (union) // at this horizontal is always, at least, a superset of the current side series // "bracketSet.horizontal" (of course, the whole series lies non-strictly inside the union). if (lastSection != null && bracketSet.coord == lastSection.coord) { assert lastSection.from() <= bracketSet.horizontal.from : "sides series sorted incorrectly"; if (bracketSet.horizontal.to <= lastSection.to()) { // We did not leave the previous section: joning to previous section lastSection.boundaryLinksAtSection.addAll(bracketSet.horizontal.containedBoundaryLinks); continue; } } final FrameSide left = bracketSet.maxLeftBeloningToUnion(); final FrameSide right = bracketSet.minRightBeloningToUnion(); assert left != null; assert right != null; assert left.coord() <= right.coord(); // The range left..right is the current horizontal section lastSection = new HorizontalSection(true, bracketSet.coord, left, right); lastSection.boundaryLinksAtSection.addAll(bracketSet.horizontal.containedBoundaryLinks); result.add(lastSection); } } return result; } private List doFindHorizontalSections() { assert !frames.isEmpty(); final List result = new ArrayList<>(); final HorizontalBoundaryIBracketSet bracketSet = new HorizontalBoundaryIBracketSet<>(allHorizontalBoundaryLinks()); HorizontalSection lastSection = null; while (bracketSet.next()) { if (bracketSet.horizontal.atFirstOfTwoParallelSides()) { // We should remember that the current section of the whole figure (union) // at this horizontal is always, at least, a superset of the current side series // "bracketSet.horizontal" (of course, the whole series lies non-strictly inside the union). if (lastSection != null && bracketSet.coord == lastSection.coord) { assert lastSection.from() <= bracketSet.horizontal.from : "sides series sorted incorrectly"; if (bracketSet.horizontal.to <= lastSection.to()) { // We did not leave the previous section: joning to previous section lastSection.boundaryLinksAtSection.add(bracketSet.horizontal); continue; } } final int leftIndex = bracketSet.maxLeftIndexBeloningToUnion(); final int rightIndex = bracketSet.minRightIndexBeloningToUnion(); assert leftIndex <= rightIndex; // The range left..right is the current horizontal section final long left = allDifferentXAtBoundary[leftIndex]; final long right = allDifferentXAtBoundary[rightIndex]; lastSection = new HorizontalSection(true, bracketSet.coord, left, right, leftIndex, rightIndex); lastSection.boundaryLinksAtSection.add(bracketSet.horizontal); result.add(lastSection); } } return result; } private List> doFindClosingLinksIntersectingEachVertical() { assert !frames.isEmpty(); final List> result = createListOfLists(allDifferentXAtBoundary.length - 1); for (HorizontalSideSeries series : horizontalSideSeriesAtBoundary) { assert series.containsBoundary(); if (!series.first) { for (HorizontalBoundaryLink closingLink : series.containedBoundaryLinks) { final int from = closingLink.transversalSeriesFrom.numberOfLessCoordinatesAtBoundary; final int to = closingLink.transversalSeriesTo.numberOfLessCoordinatesAtBoundary; for (int k = from; k < to; k++) { result.get(k).add(closingLink); } } } } return result; } private SearchIRectangleInHypograph doFindLargestRegtangles( List horizontalSectionsByLowerSides, List> closingLinksIntersectingEachVertical) { assert !frames.isEmpty(); assert allDifferentXAtBoundary.length >= 2 : "If frames exist, they cannot have <2 vertical boundary links"; final SearchIRectangleInHypograph searcher = new SearchIRectangleInHypograph(allDifferentXAtBoundary); final long[] workY = new long[allDifferentXAtBoundary.length - 1]; for (HorizontalSection section : horizontalSectionsByLowerSides) { searcher.setCurrentFromY(section.coord); for (HorizontalBoundaryLink openingLink : section.boundaryLinksAtSection) { // Usually 1-2 horizontal links. // We need to recalculate the hypograph above these links; // above other parts of this section it was recalculated before this. final int from = openingLink.transversalSeriesFrom.numberOfLessCoordinatesAtBoundary; final int to = openingLink.transversalSeriesTo.numberOfLessCoordinatesAtBoundary; Arrays.fill(workY, from, to, Long.MAX_VALUE); for (int k = from; k < to; k++) { for (HorizontalBoundaryLink closingLink : closingLinksIntersectingEachVertical.get(k)) { final long y = closingLink.coord(); if (y >= section.coord && y < workY[k]) { // Important: >=, not >! The can be second (closing) links // exactly at the same horizontal. workY[k] = y; } // It is interesting that we can just ignore here all first (opening) // horizontal links: inside all the horizontal section, which we should // analyse, the next horizontal openingLink is always second (closing) } } for (int k = from; k < to; k++) { searcher.setY(k, workY[k]); } } if (DEBUG_LEVEL >= 3) { // Warning: it leads to incorrect global largest rectangle! searcher.resetAlreadyFoundRectangle(); } searcher.resetMaxRectangleCorrected(); searcher.correctMaximalRectangle( section.leftNumberOfLessCoordinatesAtBoundary, section.rightNumberOfLessCoordinatesAtBoundary); // no special action required for removed horizontal links if (searcher.isMaxRectangleCorrected()) { section.largestRectangle = searcher.largestRectangle(); } } return searcher; } static void debug(int level, String format, Object... args) { if (DEBUG_LEVEL >= level) { System.out.printf(Locale.US, " IRU " + format, args); } } private static void addConnection(List> connectionLists, Frame a, Frame b) { connectionLists.get(a.index).add(b); connectionLists.get(b.index).add(a); // If we even adds a connection twice, it is not a problem for searching connected components. } private static void addHorizontalLink( HorizontalIBracketSet bracketSet, FrameSide firstTransveral, FrameSide secondTransveral) { assert firstTransveral.coord() <= secondTransveral.coord(); final HorizontalBoundaryLink link = new HorizontalBoundaryLink( bracketSet.horizontal, (VerticalSideSeries) firstTransveral.parentSeries, (VerticalSideSeries) secondTransveral.parentSeries); if (link.from < link.to) { if (DEBUG_LEVEL >= 3) { debug(3, " adding %s%n", link); } bracketSet.horizontal.addLink(link); } } private static List scanBoundary(HorizontalBoundaryLink start, long maxCount) { List result = new ArrayList<>(); HorizontalBoundaryLink hLink = start; VerticalBoundaryLink vLink = start.linkTo; long count = 0; do { assert vLink.linkFrom != null && vLink.linkTo != null; assert hLink == vLink.linkFrom || hLink == vLink.linkTo; assert !hLink.joinedIntoAllBoundaries; assert !vLink.joinedIntoAllBoundaries; result.add(hLink); result.add(vLink); hLink.joinedIntoAllBoundaries = true; vLink.joinedIntoAllBoundaries = true; hLink = hLink == vLink.linkFrom ? vLink.linkTo : vLink.linkFrom; assert hLink.linkFrom != null && hLink.linkTo != null; vLink = vLink == hLink.linkFrom ? hLink.linkTo : hLink.linkFrom; if (++count > maxCount) { throw new AssertionError("Infinite loop detected while scanning the boundary"); } } while (hLink != start); return result; } private static List checkAndConvertToFrames(Collection rectangles) { Objects.requireNonNull(rectangles, "Null rectangles argument"); for (IRectangularArea rectangle : rectangles) { Objects.requireNonNull(rectangle, "Null rectangle in a collection"); if (rectangle.coordCount() != 2) { throw new IllegalArgumentException("Only 2-dimensional rectangles can be joined"); } } long t1 = System.nanoTime(); List result = new ArrayList<>(); int index = 0; for (IRectangularArea rectangle : rectangles) { result.add(new Frame(rectangle, index++)); } long t2 = System.nanoTime(); debug(1, "Rectangle union (%d rectangles), initial allocating frames: %.3f ms%n", result.size(), (t2 - t1) * 1e-6); return result; } private static List cloneFrames(Collection frames) { assert frames != null; long t1 = System.nanoTime(); List result = new ArrayList<>(); int index = 0; for (Frame frame : frames) { result.add(new Frame(frame.rectangle, index++)); // It is necessary to create new fields of Frame objects: // lessHorizontalSide, higherHorizontalSide, lessVerticalSide, higherVerticalSide // The current values of these fields provide access to SideSeries (parentSeries field) // and then to lists of links, which have no relation to newly created union. } long t2 = System.nanoTime(); debug(1, "Rectangle union (%d rectangles), initial cloning frames: %.3f ms%n", result.size(), (t2 - t1) * 1e-6); return result; } private static List> createListOfLists(int n) { final List> result = new ArrayList<>(); for (int k = 0; k < n; k++) { result.add(new ArrayList<>()); } return result; } private static long totalCount(List> listOfLists) { long result = 0; for (List list : listOfLists) { result += list.size(); } return result; } // The following class is necessary for finding boundary: // we must join sides that can be a single boundary link // to avoid troubles with links which lie at several frame sides. static abstract class SideSeries extends Side { final long coord; long from; long to; private FrameSide sideFrom; private FrameSide sideTo; private final FrameSide firstSide; private List otherSides = null; int indexInSortedListAtBoundary = -1; int numberOfLessCoordinatesAtBoundary = -1; private SideSeries(FrameSide initialSide) { super(initialSide.first); this.coord = initialSide.coord(); this.from = initialSide.from(); this.to = initialSide.to(); this.sideFrom = initialSide.transversalFrameSideFrom(); this.sideTo = initialSide.transversalFrameSideTo(); this.firstSide = initialSide; initialSide.parentSeries = this; } @Override public long coord() { return coord; } @Override public long from() { return from; } @Override public long to() { return to; } @Override void allContainedFrameSides(List result) { result.clear(); result.add(firstSide); if (otherSides != null) { result.addAll(otherSides); } } @Override FrameSide transversalFrameSideFrom() { return sideFrom; } @Override FrameSide transversalFrameSideTo() { return sideTo; } boolean expand(FrameSide followingSide) { if (followingSide.isHorizontal() != this.isHorizontal() || followingSide.coord() != this.coord() || followingSide.first != this.first) { return false; } final long followingFrom = followingSide.from(); final long followingTo = followingSide.to(); if (followingFrom > to || followingTo < from) { return false; } if (followingFrom < from) { from = followingFrom; sideFrom = followingSide.transversalFrameSideFrom(); } if (followingTo > to) { to = followingTo; sideTo = followingSide.transversalFrameSideTo(); } if (otherSides == null) { otherSides = new ArrayList<>(); } otherSides.add(followingSide); followingSide.parentSeries = this; return true; } } static class HorizontalSideSeries extends SideSeries { private List containedBoundaryLinks = null; // null means an emoty list: it saves memory and time for allocation // (in real applications most of series do not contain links usually) private HorizontalSideSeries(FrameSide initialSide) { super(initialSide); } @Override public boolean isHorizontal() { return true; } private void addLink(HorizontalBoundaryLink link) { if (containedBoundaryLinks == null) { containedBoundaryLinks = new ArrayList<>(); } containedBoundaryLinks.add(link); } private boolean containsBoundary() { return containedBoundaryLinks != null; } private int containedLinksCount() { return containedBoundaryLinks == null ? 0 : containedBoundaryLinks.size(); } } static class VerticalSideSeries extends SideSeries { private List containedBoundaryLinks = null; private List intersectingBoundaryLinks = null; // null means an emoty list: it saves memory and time for allocation // (in real applications most of series do not contain links usually) private VerticalSideSeries(FrameSide initialSide) { super(initialSide); } @Override public boolean isHorizontal() { return false; } private void addLink(VerticalBoundaryLink link) { if (containedBoundaryLinks == null) { containedBoundaryLinks = new ArrayList<>(); } containedBoundaryLinks.add(link); } private void addIntersectingLink(HorizontalBoundaryLink link) { if (intersectingBoundaryLinks == null) { intersectingBoundaryLinks = new ArrayList<>(); } intersectingBoundaryLinks.add(link); } private boolean containsBoundary() { return containedBoundaryLinks != null; } private int containedLinksCount() { return containedBoundaryLinks == null ? 0 : containedBoundaryLinks.size(); } } static class HorizontalSection extends Side { private final long coord; private final long left; private final long right; private final int leftNumberOfLessCoordinatesAtBoundary; private final int rightNumberOfLessCoordinatesAtBoundary; private final List boundaryLinksAtSection = new ArrayList<>(); private IRectangularArea largestRectangle = null; // - can be accessed via reflection for debugging needs private HorizontalSection(boolean first, long coord, FrameSide left, FrameSide right) { this( first, coord, left.coord(), right.coord(), left.parentSeries.numberOfLessCoordinatesAtBoundary, right.parentSeries.numberOfLessCoordinatesAtBoundary); } private HorizontalSection( boolean first, long coord, long left, long right, int leftNumberOfLessCoordinatesAtBoundary, int rightNumberOfLessCoordinatesAtBoundary) { super(first); this.coord = coord; this.left = left; this.right = right; this.leftNumberOfLessCoordinatesAtBoundary = leftNumberOfLessCoordinatesAtBoundary; this.rightNumberOfLessCoordinatesAtBoundary = rightNumberOfLessCoordinatesAtBoundary; } @Override public boolean isHorizontal() { return true; } @Override public long coord() { return coord; } @Override public long from() { return left; } @Override public long to() { return right; } @Override void allContainedFrameSides(List result) { throw new UnsupportedOperationException(); } @Override FrameSide transversalFrameSideFrom() { throw new UnsupportedOperationException(); } @Override FrameSide transversalFrameSideTo() { throw new UnsupportedOperationException(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy