UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

206 lines (167 loc) 6.45 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { MathUtils, Spherical, Vector3 } from 'three'; import Coordinates from './Coordinates'; import CoordinateSystem from './CoordinateSystem'; import Ellipsoid from './Ellipsoid'; function computeJulianDate(date: Date): number { let year = date.getUTCFullYear(); let month = date.getUTCMonth() + 1; const day = date.getUTCDate(); const hour = date.getUTCHours(); const minute = date.getUTCMinutes(); const second = date.getUTCSeconds(); const dayFraction = (hour + minute / 60 + second / 3600) / 24; if (month <= 2) { year -= 1; month += 12; } const A = Math.floor(year / 100); const B = 2 - A + Math.floor(A / 4); const JD0h = Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5; return JD0h + dayFraction; } function normalizedDegreesLongitude(degrees: number): number { const lon = degrees % 360; return lon > 180 ? lon - 360 : lon < -180 ? 360 + lon : lon; } function normalizeAngle360(degrees: number): number { const angle = degrees % 360; return angle >= 0 ? angle : angle < 0 ? 360 + angle : 360 - angle; } interface Celestial { rightAscension: number; declination: number; } function celestialToGeographic( celestialLocation: Celestial, date: Date, ): { latitude: number; longitude: number } { const julianDate = computeJulianDate(date); //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0) const numDays = julianDate - 2451545; //Greenwich Mean Sidereal Time const GMST = normalizeAngle360(280.46061837 + 360.98564736629 * numDays); //Greenwich Hour Angle const GHA = normalizeAngle360(GMST - celestialLocation.rightAscension); const longitude = normalizedDegreesLongitude(-GHA); return { latitude: celestialLocation.declination, longitude: longitude, }; } /** * Gets the position of the sun in [**equatorial coordinates**](https://en.wikipedia.org/wiki/Position_of_the_Sun#Equatorial_coordinates) * at the given date. * * Note: the geographic position of the sun is the location on earth where the sun is at the zenith. * @param date - The date to compute the geographic position. If unspecified, the current date is used. * @returns The geographic position of the sun at the given date. */ export function getGeographicPosition(date?: Date, target?: Coordinates): Coordinates { date = date ?? new Date(); const JD = computeJulianDate(date); const numDays = JD - 2451545; // Mean longitude of the sun, in degrees const meanLongitude = normalizeAngle360(280.46 + 0.9856474 * numDays); // Mean anomaly of the sun, in radians const meanAnomalyRad = normalizeAngle360(357.528 + 0.9856003 * numDays) * MathUtils.DEG2RAD; // Ecliptic longitude of the sun, in degrees const eclipticLongitude = meanLongitude + 1.915 * Math.sin(meanAnomalyRad) + 0.02 * Math.sin(2 * meanAnomalyRad); const eclipticLongitudeRad = eclipticLongitude * MathUtils.DEG2RAD; // Obliquity of the ecliptic, in radians const obliquityOfTheEcliptic = MathUtils.DEG2RAD * (23.439 - 0.0000004 * numDays); const declination = Math.asin(Math.sin(obliquityOfTheEcliptic) * Math.sin(eclipticLongitudeRad)) * MathUtils.RAD2DEG; let rightAscension = Math.atan(Math.cos(obliquityOfTheEcliptic) * Math.tan(eclipticLongitudeRad)) * MathUtils.RAD2DEG; //compensate for atan result if (eclipticLongitude >= 90 && eclipticLongitude < 270) { rightAscension += 180; } rightAscension = normalizeAngle360(rightAscension); const { latitude, longitude } = celestialToGeographic({ rightAscension, declination }, date); target = target ?? new Coordinates(CoordinateSystem.epsg4326, 0, 0); target.set(CoordinateSystem.epsg4326, longitude, latitude); return target; } /** * Returns the local position of the sun, given the zenith and azimuth. */ export function getLocalPosition( params: { /** * The zenith of the sun, in degrees, in horizontal coordinates. * * Note: the value is clamped to the [0°, 90°] range. */ zenith: number; /** * The azimuth of the sun, in degrees, in horizontal coordinates */ azimuth: number; /** * The local point. * @defaultValue (0, 0, 0) */ point?: Vector3; /** * The distance of the sun to the local point. * @defaultValue 1 */ distance?: number; }, target?: Vector3, ): Vector3 { const zenith = MathUtils.clamp(params.zenith, 0, 90); const point = params.point ?? new Vector3(0, 0, 0); const theta = MathUtils.degToRad(params.azimuth); const phi = MathUtils.degToRad(zenith); const spherical = new Spherical(params.distance ?? 1, phi, theta); target = target ?? new Vector3(); const raw = target.setFromSpherical(spherical); // The spherical is Y-up, but we are Z-up const { y, z } = raw; raw.setY(z); raw.setZ(y); return raw.add(point); } /** * Gets the direction vector of sun rays at a given date, in the ECEF coordinate system. */ export function getDirection(date?: Date): Vector3 { const sunGeo = getGeographicPosition(date); const dir = Ellipsoid.WGS84.toCartesian(sunGeo.latitude, sunGeo.longitude, 0).normalize(); return dir.negate(); } /** * Gets the direction vector of sun rays at a given date in the ENU * coordinate system centered at the observer location. * Note: this assumes that the target coordinate system is north up. */ export function getLocalFrameDirection(observer: Coordinates, date?: Date): Vector3 { const observerGeo = observer.as(CoordinateSystem.epsg4326); const observerFrame = Ellipsoid.WGS84.getEastNorthUpMatrix( observerGeo.latitude, observerGeo.longitude, ); const sunCartesian = getDirection(date); const sunLocal = sunCartesian.applyMatrix4(observerFrame.invert()); return sunLocal; } /** * Utility functions related to the position of the sun. */ export default { getGeographicPosition, getLocalPosition, getLocalFrameDirection, getDirection, };