[astro] Tweak sun and moon position classes (#20063)

* Revamp sun and moon position

Signed-off-by: gael@lhopital.org <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital
2026-01-14 22:24:24 +01:00
committed by GitHub
parent cf530188a1
commit b584fe9737
9 changed files with 97 additions and 69 deletions
@@ -33,6 +33,7 @@ import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.State;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@@ -77,9 +78,14 @@ public class AstroActions implements ThingActions {
public @Nullable @ActionOutput(name = "result", label = "Azimuth", type = "org.openhab.core.library.types.QuantityType<javax.measure.quantity.Angle>") QuantityType<Angle> getAzimuth(
@ActionInput(name = "date", label = "Date", required = false, description = "Considered date") @Nullable ZonedDateTime date) {
logger.debug("Astro action 'getAzimuth' called");
AstroThingHandler theHandler = this.handler;
if (theHandler != null) {
return theHandler.getAzimuth(date != null ? date : ZonedDateTime.now(timeZoneProvider.getTimeZone()));
if (handler instanceof AstroThingHandler local) {
State result = local.getAzimuth(date != null ? date : ZonedDateTime.now(timeZoneProvider.getTimeZone()));
if (result instanceof QuantityType<?>) {
@SuppressWarnings("unchecked")
QuantityType<Angle> qta = (QuantityType<Angle>) result;
return qta;
}
return null;
} else {
logger.info("Astro Action service ThingHandler is null!");
}
@@ -90,9 +96,14 @@ public class AstroActions implements ThingActions {
public @Nullable @ActionOutput(name = "result", label = "Elevation", type = "org.openhab.core.library.types.QuantityType<javax.measure.quantity.Angle>") QuantityType<Angle> getElevation(
@ActionInput(name = "date", label = "Date", required = false, description = "Considered date") @Nullable ZonedDateTime date) {
logger.debug("Astro action 'getElevation' called");
AstroThingHandler theHandler = this.handler;
if (theHandler != null) {
return theHandler.getElevation(date != null ? date : ZonedDateTime.now(timeZoneProvider.getTimeZone()));
if (handler instanceof AstroThingHandler local) {
State result = local.getElevation(date != null ? date : ZonedDateTime.now(timeZoneProvider.getTimeZone()));
if (result instanceof QuantityType<?>) {
@SuppressWarnings("unchecked")
QuantityType<Angle> qta = (QuantityType<Angle>) result;
return qta;
}
return null;
} else {
logger.info("Astro Action service ThingHandler is null!");
}
@@ -29,6 +29,7 @@ import org.openhab.binding.astro.internal.model.EclipseType;
import org.openhab.binding.astro.internal.model.Moon;
import org.openhab.binding.astro.internal.model.MoonPhase;
import org.openhab.binding.astro.internal.model.MoonPhaseName;
import org.openhab.binding.astro.internal.model.MoonPosition;
import org.openhab.binding.astro.internal.model.Position;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
@@ -92,7 +93,7 @@ public class MoonCalc {
double jdate = getEclipse(calendar, EclipseType.MOON, julianDateMidnight, eclipseKind);
Calendar eclipseDate = DateTimeUtils.toCalendar(jdate, zone, locale);
if (eclipseDate != null) {
eclipse.set(eclipseKind, eclipseDate, new Position());
eclipse.set(eclipseKind, eclipseDate, Position.NULL);
}
});
@@ -600,10 +601,8 @@ public class MoonCalc {
double[] raDecTopo = geoEqu2TopoEqu(raDec, distance, lat, lmst);
double[] azAlt = equ2AzAlt(raDecTopo[0], raDecTopo[1], lat, lmst);
Position position = moon.getPosition();
position.setAzimuth(Math.toDegrees(azAlt[0]));
position.setElevation(Math.toDegrees(azAlt[1]) + refraction(azAlt[1]));
moon.setPosition(
new MoonPosition(Math.toDegrees(azAlt[0]), Math.toDegrees(azAlt[1]) + refraction(azAlt[1]), moonLon));
moon.setZodiac(ZodiacCalc.calculate(moonLon, null));
}
@@ -83,12 +83,8 @@ public class SunCalc {
double azimuth = Math.toDegrees(getAzimuth(th, a, phi, d));
double elevation = Math.toDegrees(getElevation(th, a, phi, d));
double shadeLength = getShadeLength(elevation);
Position position = sun.getPosition();
position.setAzimuth(azimuth + 180);
position.setElevation(elevation);
position.setShadeLength(shadeLength);
sun.setPosition(new Position(azimuth + 180, elevation));
}
/**
@@ -232,7 +228,7 @@ public class SunCalc {
double jdate = mc.getEclipse(calendar, EclipseType.SUN, j, eclipseKind);
Calendar eclipseDate = DateTimeUtils.toCalendar(jdate, zone, locale);
if (eclipseDate != null) {
eclipse.set(eclipseKind, eclipseDate, new Position());
eclipse.set(eclipseKind, eclipseDate, Position.NULL);
}
});
@@ -315,10 +311,6 @@ public class SunCalc {
return Math.asin(Math.sin(phi) * Math.sin(d) + Math.cos(phi) * Math.cos(d) * Math.cos(th - a));
}
private double getShadeLength(double elevation) {
return 1 / MathUtils.tanDeg(elevation);
}
private double getHourAngle(double h, double phi, double d) {
return Math.acos((Math.sin(h) - Math.sin(phi) * Math.sin(d)) / (Math.cos(phi) * Math.cos(d)));
}
@@ -35,8 +35,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.measure.quantity.Angle;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.action.AstroActions;
@@ -49,7 +47,6 @@ import org.openhab.binding.astro.internal.model.Position;
import org.openhab.binding.astro.internal.util.PropertyUtils;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.scheduler.ScheduledCompletableFuture;
import org.openhab.core.thing.Channel;
@@ -59,6 +56,8 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -402,14 +401,14 @@ public abstract class AstroThingHandler extends BaseThingHandler {
public abstract @Nullable Position getPositionAt(ZonedDateTime date);
public @Nullable QuantityType<Angle> getAzimuth(ZonedDateTime date) {
public State getAzimuth(ZonedDateTime date) {
Position position = getPositionAt(date);
return position != null ? position.getAzimuth() : null;
return position != null ? position.getAzimuth() : UnDefType.NULL;
}
public @Nullable QuantityType<Angle> getElevation(ZonedDateTime date) {
public State getElevation(ZonedDateTime date) {
Position position = getPositionAt(date);
return position != null ? position.getElevation() : null;
return position != null ? position.getElevation() : UnDefType.NULL;
}
@Override
@@ -30,7 +30,7 @@ public class Moon extends RiseSet implements Planet {
private MoonPhase phase = new MoonPhase();
private Eclipse eclipse = new Eclipse(EclipseKind.PARTIAL, EclipseKind.TOTAL);
private Position position = new Position();
private Position position = MoonPosition.NULL;
private Zodiac zodiac = Zodiac.NULL;
public Moon() {
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.astro.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Immutable class holding Moon Position information
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class MoonPosition extends Position {
public static final Position NULL = new MoonPosition();
private final double longitude;
private MoonPosition() {
super(Double.NaN, Double.NaN);
this.longitude = Double.NaN;
}
public MoonPosition(double azimuth, double elevation, double longitude) {
super(azimuth, elevation);
this.longitude = longitude;
}
/**
* Returns the moon longitude
*/
public double getLongitude() {
return longitude;
}
}
@@ -12,14 +12,16 @@
*/
package org.openhab.binding.astro.internal.model;
import javax.measure.quantity.Angle;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.util.MathUtils;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* Holds the calculated azimuth and elevation.
* Immutable class holding calculated azimuth and elevation.
*
* @author Gerhard Riegler - Initial contribution
* @author Gaël L'hopital - Added shade length
@@ -27,39 +29,35 @@ import org.openhab.core.library.unit.Units;
*/
@NonNullByDefault
public class Position {
public static final Position NULL = new Position();
private final double azimuth;
private final double elevation;
private double azimuth;
private double elevation;
private double shadeLength;
public Position() {
private Position() {
this(Double.NaN, Double.NaN);
}
public Position(double azimuth, double elevation, double shadeLength) {
public Position(double azimuth, double elevation) {
this.azimuth = azimuth;
this.elevation = elevation;
this.shadeLength = shadeLength;
}
/**
* Returns the azimuth.
*/
public QuantityType<Angle> getAzimuth() {
return new QuantityType<>(azimuth, Units.DEGREE_ANGLE);
public State getAzimuth() {
return Double.isNaN(azimuth) ? UnDefType.UNDEF : new QuantityType<>(azimuth, Units.DEGREE_ANGLE);
}
/**
* Sets the azimuth.
*/
public void setAzimuth(double azimuth) {
this.azimuth = azimuth;
public double getAzimuthAsDouble() {
return azimuth;
}
/**
* Returns the elevation.
*/
public QuantityType<Angle> getElevation() {
return new QuantityType<>(elevation, Units.DEGREE_ANGLE);
public State getElevation() {
return Double.isNaN(elevation) ? UnDefType.UNDEF : new QuantityType<>(elevation, Units.DEGREE_ANGLE);
}
public double getElevationAsDouble() {
@@ -67,23 +65,10 @@ public class Position {
}
/**
* Sets the elevation.
* Returns the shade length ratio.
*/
public void setElevation(double elevation) {
this.elevation = elevation;
}
/**
* Returns the shade length.
*/
public double getShadeLength() {
return shadeLength;
}
/**
* Sets the shade length.
*/
public void setShadeLength(double shadeLength) {
this.shadeLength = shadeLength;
public State getShadeLength() {
return Double.isNaN(elevation) ? UnDefType.UNDEF
: elevation <= 0 ? UnDefType.NULL : new DecimalType(1d / MathUtils.tanDeg(elevation));
}
}
@@ -28,8 +28,7 @@ public class Sun extends RiseSet implements Planet {
private Map<SunPhaseName, Range> ranges = new HashMap<>();
private Position position = new Position();
private Position position = Position.NULL;
private Zodiac zodiac = Zodiac.NULL;
private @Nullable Season season = null;
@@ -110,8 +110,8 @@ public class MoonCalcTest {
moonCalc.setPositionalInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, moon, TIME_ZONE, Locale.ROOT);
// expected result from heavens-above.com is Azimuth: 100.5, altitude -17
assertEquals(100.5, moon.getPosition().getAzimuth().doubleValue(), ACCURACY_IN_DEGREE);
assertEquals(-17, moon.getPosition().getElevation().doubleValue(), ACCURACY_IN_DEGREE);
assertEquals(100.5, moon.getPosition().getAzimuthAsDouble(), ACCURACY_IN_DEGREE);
assertEquals(-17, moon.getPosition().getElevationAsDouble(), ACCURACY_IN_DEGREE);
}
@Test