MapcodeZone.java

/*
 * Copyright (C) 2014-2017, Stichting Mapcode Foundation (http://www.mapcode.com)
 *
 * 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 com.mapcode;

import javax.annotation.Nonnull;

// ----------------------------------------------------------------------------------------------
// Package private implementation class. For internal use within the Mapcode implementation only.
// ----------------------------------------------------------------------------------------------

/**
 * Simple class to represent all the coordinates that would deliver a particular mapcode.
 */
class MapcodeZone {

    // TODO: Explain why you need these fractions and how they work exactly.
    // Longitudes in LonFractions ("1/3240 billionths").
    private double lonFractionMin;
    private double lonFractionMax;

    // Latitudes in LatFractions ("1/810 billionths").
    private double latFractionMin;
    private double latFractionMax;

    MapcodeZone(final double latFractionMin, final double latFractionMax,
                final double lonFractionMin, final double lonFractionMax) {
        this.latFractionMin = latFractionMin;
        this.latFractionMax = latFractionMax;
        this.lonFractionMin = lonFractionMin;
        this.lonFractionMax = lonFractionMax;
    }

    MapcodeZone(@Nonnull final MapcodeZone mapcodeZone) {
        this(mapcodeZone.latFractionMin, mapcodeZone.latFractionMax, mapcodeZone.lonFractionMin, mapcodeZone.lonFractionMax);
    }

    MapcodeZone() {
        this(0.0, 0.0, 0.0, 0.0);
    }

    double getLonFractionMin() {
        return lonFractionMin;
    }

    double getLonFractionMax() {
        return lonFractionMax;
    }

    double getLatFractionMin() {
        return latFractionMin;
    }

    double getLatFractionMax() {
        return latFractionMax;
    }

    void setLonFractionMin(final double lonFractionMin) {
        this.lonFractionMin = lonFractionMin;
    }

    void setLonFractionMax(final double lonFractionMax) {
        this.lonFractionMax = lonFractionMax;
    }

    void setLatFractionMin(final double latFractionMin) {
        this.latFractionMin = latFractionMin;
    }

    void setLatFractionMax(final double latFractionMax) {
        this.latFractionMax = latFractionMax;
    }

    // TODO: Use of this method is unclear.
    // Generate upper and lower limits based on x and y, and delta's.
    void setFromFractions(final double latFraction, final double lonFraction,
                          final double latFractionDelta, final double lonFractionDelta) {
        assert (lonFractionDelta >= 0.0);
        assert (latFractionDelta != 0.0);
        lonFractionMin = lonFraction;
        lonFractionMax = lonFraction + lonFractionDelta;
        if (latFractionDelta < 0) {
            latFractionMin = latFraction + 1 + latFractionDelta;    // y + yDelta can NOT be represented.
            latFractionMax = latFraction + 1;                       // y CAN be represented.
        } else {
            latFractionMin = latFraction;
            latFractionMax = latFraction + latFractionDelta;
        }
    }

    boolean isEmpty() {
        return ((lonFractionMax <= lonFractionMin) || (latFractionMax <= latFractionMin));
    }

    // TODO: Explain what this is (geo point of mapcode).
    @Nonnull
    Point getCenter() {
        if (isEmpty()) {
            return Point.undefined();
        } else {
            final double latFrac = Math.floor((latFractionMin + latFractionMax) / 2.0);
            final double lonFrac = Math.floor((lonFractionMin + lonFractionMax) / 2.0);
            return Point.fromLatLonFractions(latFrac, lonFrac);
        }
    }

    // TODO: Explain when this is used. It clips a zone to an encompassing boundary.
    // Returns a non-empty intersection of a mapcode zone and a territory area.
    // Returns null if no such intersection exists.
    @Nonnull
    MapcodeZone restrictZoneTo(@Nonnull final Boundary area) {
        final MapcodeZone mapcodeZone = new MapcodeZone(latFractionMin, latFractionMax, lonFractionMin, lonFractionMax);
        final double latMin = area.getLatMicroDegMin() * Point.LAT_MICRODEG_TO_FRACTIONS_FACTOR;
        if (mapcodeZone.latFractionMin < latMin) {
            mapcodeZone.latFractionMin = latMin;
        }
        final double latMax = area.getLatMicroDegMax() * Point.LAT_MICRODEG_TO_FRACTIONS_FACTOR;
        if (mapcodeZone.latFractionMax > latMax) {
            mapcodeZone.latFractionMax = latMax;
        }
        if (mapcodeZone.latFractionMin < mapcodeZone.latFractionMax) {
            double lonMin = area.getLonMicroDegMin() * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR;
            double lonMax = area.getLonMicroDegMax() * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR;
            if ((lonMax < 0) && (mapcodeZone.lonFractionMin > 0)) {
                lonMin += (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
                lonMax += (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
            } else if ((lonMin > 1) && (mapcodeZone.lonFractionMax < 0)) {
                lonMin -= (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
                lonMax -= (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
            }
            if (mapcodeZone.lonFractionMin < lonMin) {
                mapcodeZone.lonFractionMin = lonMin;
            }
            if (mapcodeZone.lonFractionMax > lonMax) {
                mapcodeZone.lonFractionMax = lonMax;
            }
        }
        return mapcodeZone;
    }

    @Nonnull
    @Override
    public String toString() {
        return isEmpty() ? "empty" : ("[" + (latFractionMin / Point.LAT_TO_FRACTIONS_FACTOR) + ", " +
                (latFractionMax / Point.LAT_TO_FRACTIONS_FACTOR) +
                "), [" + (lonFractionMin / Point.LON_TO_FRACTIONS_FACTOR) + ", " +
                (lonFractionMax / Point.LON_TO_FRACTIONS_FACTOR) + ')');
    }
}