001package ball.util;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.awt.Point;
022import java.awt.geom.Point2D;
023import java.beans.ConstructorProperties;
024import java.io.Serializable;
025import java.util.Arrays;
026import java.util.Comparator;
027import java.util.Objects;
028import java.util.SortedSet;
029import java.util.TreeSet;
030
031/**
032 * X-Y coordinate representation.
033 *
034 * {@bean.info}
035 *
036 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
037 */
038public class Coordinate implements Comparable<Coordinate>, Serializable {
039    private static final long serialVersionUID = -4428900365914120909L;
040
041    private static final Comparator<? super Coordinate> COMPARATOR =
042        Comparator
043        .comparingInt(Coordinate::getY)
044        .thenComparingInt(Coordinate::getX);
045
046    /** @serial */ private final int y;
047    /** @serial */ private final int x;
048
049    /**
050     * @param   y               The Y-coordinate.
051     * @param   x               The X-coordinate.
052     */
053    @ConstructorProperties({ "y", "x" })
054    public Coordinate(Number y, Number x) { this(y.intValue(), x.intValue()); }
055
056    private Coordinate(int y, int x) {
057        this.y = y;
058        this.x = x;
059    }
060
061    public int getY() { return y; }
062
063    public int getX() { return x; }
064
065    /**
066     * Method to get the {@link Coordinate} relative to {@link.this}
067     * {@link Coordinate}.
068     *
069     * @param   dy              The change in Y-coordinate.
070     * @param   dx              The change in X-coordinate.
071     *
072     * @return  The relative {@link Coordinate}.
073     */
074    public Coordinate translate(Number dy, Number dx) {
075        return new Coordinate(getY() + dy.intValue(), getX() + dx.intValue());
076    }
077
078    /**
079     * Method to getthe {@link Coordinate} relative to {@link.this}
080     * {@link Coordinate}.
081     *
082     * @param   ds              The {@link Coordinate} describing the change.
083     *
084     * @return  The relative {@link Coordinate}.
085     */
086    public Coordinate translate(Coordinate ds) {
087        return translate(ds.getY(), ds.getX());
088    }
089
090    /**
091     * Method to determine if {@link.this} {@link Coordinate} is within the
092     * area described by the argument {@link Coordinate}s.
093     *
094     * @param   min             The minimum {@link Coordinate}.
095     * @param   max             The maximum {@link Coordinate}.
096     *
097     * @return  {@code true} if within the area; {@code false} otherwise.
098     */
099    public boolean within(Coordinate min, Coordinate max) {
100        return ((min.getY() <= getY() && getY() < max.getY()) && (min.getX() <= getX() && getX() < max.getX()));
101    }
102
103    /**
104     * Method to translate this {@link Coordinate} to a {@link Point2D}.
105     *
106     * @return  The {@link Point}.
107     */
108    public Point2D asPoint() { return new Point(getX(), getY()); }
109
110    @Override
111    public int compareTo(Coordinate that) {
112        return Objects.compare(this, that, COMPARATOR);
113    }
114
115    @Override
116    public boolean equals(Object object) {
117        return ((object instanceof Coordinate) ? (this.compareTo((Coordinate) object) == 0) : super.equals(object));
118    }
119
120    @Override
121    public int hashCode() { return Arrays.asList(y, x).hashCode(); }
122
123    @Override
124    public String toString() { return Arrays.asList(y, x).toString(); }
125
126    /**
127     * Static method to return a {@link SortedSet} of {@link Coordinate}s
128     * specified by the parameters.
129     *
130     * @param   min             {@code [y0, x0]}
131     * @param   max             {@code [yN, xN]}
132     *
133     * @return  The {@link SortedSet} of {@link Coordinate}s.
134     */
135    public static SortedSet<Coordinate> range(Coordinate min, Coordinate max) {
136        return range(Math.min(min.getY(), max.getY()), Math.min(min.getX(), max.getX()),
137                     Math.max(min.getY(), max.getY()), Math.max(min.getX(), max.getX()));
138    }
139
140    /**
141     * Static method to return a {@link SortedSet} of {@link Coordinate}s
142     * specified by the parameters.
143     *
144     * @param   y0              {@code MIN(y)}
145     * @param   x0              {@code MIN(x)}
146     * @param   yN              {@code MAX(y) + 1}
147     * @param   xN              {@code MAX(x) + 1}
148     *
149     * @return  The {@link SortedSet} of {@link Coordinate}s.
150     */
151    public static SortedSet<Coordinate> range(int y0, int x0, int yN, int xN) {
152        TreeSet<Coordinate> set = new TreeSet<>();
153
154        for (int y = y0; y < yN; y += 1) {
155            for (int x = x0; x < xN; x += 1) {
156                set.add(new Coordinate(y, x));
157            }
158        }
159
160        return set;
161    }
162}