001/*
002 * Shredzone Commons - suncalc
003 *
004 * Copyright (C) 2017 Richard "Shred" Körber
005 *   http://commons.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.commons.suncalc.util;
015
016import static java.lang.Math.max;
017import static java.lang.Math.toRadians;
018
019import java.time.Instant;
020import java.time.LocalDate;
021import java.time.LocalDateTime;
022import java.time.LocalTime;
023import java.time.ZoneId;
024import java.time.ZonedDateTime;
025import java.time.temporal.ChronoUnit;
026import java.util.Objects;
027
028import edu.umd.cs.findbugs.annotations.Nullable;
029import org.shredzone.commons.suncalc.param.GenericParameter;
030import org.shredzone.commons.suncalc.param.LocationParameter;
031import org.shredzone.commons.suncalc.param.TimeParameter;
032
033/**
034 * A base implementation of {@link LocationParameter} and {@link TimeParameter}.
035 * <p>
036 * For internal use only.
037 *
038 * @param <T>
039 *            Type of the final builder
040 */
041@SuppressWarnings("unchecked")
042public class BaseBuilder<T> implements GenericParameter<T>, LocationParameter<T>,
043        TimeParameter<T>, Cloneable {
044
045    private @Nullable Double lat = null;
046    private @Nullable Double lng = null;
047    private double elevation = 0.0;
048    private ZonedDateTime dateTime = ZonedDateTime.now();
049
050    @Override
051    public T on(ZonedDateTime dateTime) {
052        this.dateTime = Objects.requireNonNull(dateTime, "dateTime");
053        return (T) this;
054    }
055
056    @Override
057    public T on(LocalDateTime dateTime) {
058        Objects.requireNonNull(dateTime, "dateTime");
059        return on(ZonedDateTime.of(dateTime, this.dateTime.getZone()));
060    }
061
062    @Override
063    public T on(LocalDate date) {
064        Objects.requireNonNull(date, "date");
065        return on(ZonedDateTime.of(date, LocalTime.MIDNIGHT, dateTime.getZone()));
066    }
067
068    @Override
069    public T on(Instant instant) {
070        Objects.requireNonNull(instant, "instant");
071        return on(ZonedDateTime.ofInstant(instant, dateTime.getZone()));
072    }
073
074    @Override
075    public T on(int year, int month, int date, int hour, int minute, int second) {
076        return on(ZonedDateTime.of(year, month, date, hour, minute, second, 0, dateTime.getZone()));
077    }
078
079    @Override
080    public T now() {
081        return on(ZonedDateTime.now(dateTime.getZone()));
082    }
083
084    @Override
085    public T plusDays(int days) {
086        return on(dateTime.plusDays(days));
087    }
088
089    @Override
090    public T midnight() {
091        return on(dateTime.truncatedTo(ChronoUnit.DAYS));
092    }
093
094    @Override
095    public T timezone(ZoneId tz) {
096        Objects.requireNonNull(tz, "tz");
097        on(dateTime.withZoneSameLocal(tz));
098        return (T) this;
099    }
100
101    @Override
102    public T latitude(double lat) {
103        if (lat < -90.0 || lat > 90.0) {
104            throw new IllegalArgumentException("Latitude out of range, -90.0 <= " + lat + " <= 90.0");
105        }
106        this.lat = lat;
107        return (T) this;
108    }
109
110    @Override
111    public T longitude(double lng) {
112        if (lng < -180.0 || lng > 180.0) {
113            throw new IllegalArgumentException("Longitude out of range, -180.0 <= " + lng + " <= 180.0");
114        }
115        this.lng = lng;
116        return (T) this;
117    }
118
119    @Override
120    public T elevation(double h) {
121        this.elevation = max(h, 0.0);
122        return (T) this;
123    }
124
125    @Override
126    public T sameTimeAs(TimeParameter<?> t) {
127        if (! (t instanceof BaseBuilder)) {
128            throw new IllegalArgumentException("Cannot read the TimeParameter");
129        }
130        this.dateTime = ((BaseBuilder<?>) t).dateTime;
131        return (T) this;
132    }
133
134    @Override
135    public T sameLocationAs(LocationParameter<?> l) {
136        if (! (l instanceof BaseBuilder)) {
137            throw new IllegalArgumentException("Cannot read the LocationParameter");
138        }
139        BaseBuilder<?> origin = (BaseBuilder<?>) l;
140        this.lat = origin.lat;
141        this.lng = origin.lng;
142        this.elevation = origin.elevation;
143        return (T) this;
144    }
145
146    @Override
147    public T copy() {
148        try {
149            return (T) clone();
150        } catch (CloneNotSupportedException ex) {
151            throw new RuntimeException(ex); // Should never be thrown anyway
152        }
153    }
154
155    /**
156     * Returns the longitude.
157     *
158     * @return Longitude, in degrees.
159     */
160    public double getLongitude() {
161        if (lng == null) {
162            throw new IllegalStateException("longitude is not set");
163        }
164        return lng;
165    }
166
167    /**
168     * Returns the latitude.
169     *
170     * @return Latitude, in degrees.
171     */
172    public double getLatitude() {
173        if (lat == null) {
174            throw new IllegalStateException("latitude is not set");
175        }
176        return lat;
177    }
178
179    /**
180     * Returns the longitude.
181     *
182     * @return Longitude, in radians.
183     */
184    public double getLongitudeRad() {
185        return toRadians(getLongitude());
186    }
187
188    /**
189     * Returns the latitude.
190     *
191     * @return Latitude, in radians.
192     */
193    public double getLatitudeRad() {
194        return toRadians(getLatitude());
195    }
196
197    /**
198     * Returns the elevation, in meters above sea level.
199     *
200     * @return Elevation, meters above sea level
201     */
202    public double getElevation() {
203        return elevation;
204    }
205
206    /**
207     * Returns the {@link JulianDate} to be used.
208     *
209     * @return {@link JulianDate}
210     */
211    public JulianDate getJulianDate() {
212        return new JulianDate(dateTime);
213    }
214
215    /**
216     * Returns {@code true} if a geolocation has been set.
217     *
218     * @since 3.9
219     */
220    public boolean hasLocation() {
221        return lat != null && lng != null;
222    }
223
224    /**
225     * Unset the geolocation.
226     *
227     * @since 3.9
228     */
229    public void clearLocation() {
230        lat = null;
231        lng = null;
232    }
233
234}