diff --git a/site/src/components/examples/SolarTerminator.jsx b/site/src/components/examples/SolarTerminator.jsx new file mode 100644 index 0000000..0fd7fbf --- /dev/null +++ b/site/src/components/examples/SolarTerminator.jsx @@ -0,0 +1,94 @@ +import Feature from 'ol/Feature.js'; +import Layer from '../../../../lib/layer/Vector.js'; +import Map from '../../../../lib/Map.js'; +import OSM from '../../../../lib/source/OSM.js'; +import React, {useEffect, useState} from 'react'; +import Source from '../../../../lib/source/Vector.js'; +import TileLayer from '../../../../lib/layer/WebGLTile.js'; +import View from '../../../../lib/View.js'; +import {getNightGeometry} from './solar.js'; + +const now = new Date(); + +// reuse a single feature, update the geometry as needed +const feature = new Feature(); + +/** + * @param {Date} date Input date. + * @return {number} The day number (starting with 1). + */ +function getDayOfYear(date) { + return ( + 1 + + Math.floor((date.getTime() - Date.UTC(date.getUTCFullYear())) / 86400000) + ); +} + +/** + * @param {number} UTC year. + * @param {number} UTC day number (starting with 1). + * @param {number} UTC hour. + * @return {Date} The date. + */ +function getDate(year, day, hour) { + const date = new Date(Date.UTC(year)); + date.setUTCDate(day); + date.setUTCHours(hour, 60 * (hour % 1)); + return date; +} + +function SolarTerminator() { + const [day, updateDay] = useState(getDayOfYear(now)); + const [hour, updateHour] = useState( + now.getUTCHours() + now.getUTCMinutes() / 60 + ); + + useEffect(() => { + const date = getDate(now.getUTCFullYear(), day, hour); + const polygon = getNightGeometry(date, 'EPSG:3857'); + feature.setGeometry(polygon); + }, [day, hour]); + + return ( + <> + + + + + + + + + +
+ {getDate(now.getUTCFullYear(), day, hour).toUTCString()} +
+ + +
+ + ); +} + +export default SolarTerminator; diff --git a/site/src/components/examples/solar.js b/site/src/components/examples/solar.js new file mode 100644 index 0000000..522eb3e --- /dev/null +++ b/site/src/components/examples/solar.js @@ -0,0 +1,123 @@ +/** + * Includes functions derived from https://github.com/Viglino/ol-ext + * Copyright (c) 2018 Jean-Marc VIGLINO, + * released under the CeCILL-B license (French BSD license) + * (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). + */ +import Polygon from 'ol/geom/Polygon.js'; + +/** + * Compute the position of the Sun in ecliptic coordinates at julianDay. + * @see http://en.wikipedia.org/wiki/Position_of_the_Sun + * @param {number} julianDay + */ +function sunEclipticPosition(julianDay) { + const deg2rad = Math.PI / 180; + // Days since start of J2000.0 + const n = julianDay - 2451545.0; + // mean longitude of the Sun + const L = (280.46 + 0.9856474 * n) % 360; + // mean anomaly of the Sun + const g = (357.528 + 0.9856003 * n) % 360; + // ecliptic longitude of Sun + const lambda = + L + 1.915 * Math.sin(g * deg2rad) + 0.02 * Math.sin(2 * g * deg2rad); + // distance from Sun in AU + const R = + 1.00014 - + 0.01671 * Math.cos(g * deg2rad) - + 0.0014 * Math.cos(2 * g * deg2rad); + return {lambda, R}; +} + +/** + * @see http://en.wikipedia.org/wiki/Axial_tilt#Obliquity_of_the_ecliptic_.28Earth.27s_axial_tilt.29 + * @param {number} julianDay + */ +function eclipticObliquity(julianDay) { + const n = julianDay - 2451545.0; + // Julian centuries since J2000.0 + const T = n / 36525; + const epsilon = + 23.43929111 - + T * + (46.836769 / 3600 - + T * + (0.0001831 / 3600 + + T * + (0.0020034 / 3600 - + T * (0.576e-6 / 3600 - (T * 4.34e-8) / 3600)))); + return epsilon; +} + +/** + * Compute the Sun's equatorial position from its ecliptic position. + * @param {number} sunEclLon Sun longitude in degrees. + * @param {number} eclObliq Ecliptic position in degrees. + * @return {number} Position in degrees. + */ +function sunEquatorialPosition(sunEclLon, eclObliq) { + const rad2deg = 180 / Math.PI; + const deg2rad = Math.PI / 180; + + let alpha = + Math.atan(Math.cos(eclObliq * deg2rad) * Math.tan(sunEclLon * deg2rad)) * + rad2deg; + const delta = + Math.asin(Math.sin(eclObliq * deg2rad) * Math.sin(sunEclLon * deg2rad)) * + rad2deg; + + const lQuadrant = Math.floor(sunEclLon / 90) * 90; + const raQuadrant = Math.floor(alpha / 90) * 90; + alpha = alpha + (lQuadrant - raQuadrant); + + return {alpha, delta}; +} + +/** + * Get night-day terminator coordinates. + * @param {string} time DateTime string (default now). + * @param {string} projection The projection identifier. + * @return {Polygon} A polygon representing the night. + */ +export function getNightGeometry(time, projection) { + const step = 1; + const rad2deg = 180 / Math.PI; + const deg2rad = Math.PI / 180; + + const date = time ? new Date(time) : new Date(); + + // Calculate the present UTC Julian Date. + // Function is valid after the beginning of the UNIX epoch 1970-01-01 and ignores leap seconds. + const julianDay = date / 86400000 + 2440587.5; + + // Calculate Greenwich Mean Sidereal Time (low precision equation). + // http://aa.usno.navy.mil/faq/docs/GAST.php + const gst = (18.697374558 + 24.06570982441908 * (julianDay - 2451545.0)) % 24; + const coordinates = []; + + const sunEclPos = sunEclipticPosition(julianDay); + const eclObliq = eclipticObliquity(julianDay); + const sunEqPos = sunEquatorialPosition(sunEclPos.lambda, eclObliq); + + for (let i = -180; i <= 180; i += step) { + const lon = i; + // Hour angle (indegrees) of the sun for a longitude on Earth. + const ha = gst * 15 + lon - sunEqPos.alpha; + // Latitude + const lat = + Math.atan(-Math.cos(ha * deg2rad) / Math.tan(sunEqPos.delta * deg2rad)) * + rad2deg; + coordinates.push([lon, lat]); + } + + const lat = sunEqPos.delta < 0 ? 90 : -90; + for (let tlon = 180; tlon >= -180; tlon -= step) { + coordinates.push([tlon, lat]); + } + coordinates.push(coordinates[0]); + + const polygon = new Polygon([coordinates]); + polygon.transform('EPSG:4326', projection); + return polygon; +} diff --git a/site/src/content/examples/solar-terminator.mdx b/site/src/content/examples/solar-terminator.mdx new file mode 100644 index 0000000..c714aa9 --- /dev/null +++ b/site/src/content/examples/solar-terminator.mdx @@ -0,0 +1,19 @@ +--- +title: 'Solar Terminator' +level: 2 +description: | + This example shows how the `` source can be used to render a feature + that is updated with changes to a component's state variables. In this example + a single feature is rendered with a polygon geometry representing the solar + terminator (dark where it is night and light where it is day). + + The `day` and `hour` state variables are controlled by the sliders on the map. + A `useEffect` hook is called when these values change to calculate a new polygon + geometry representing the night. The vector source is constructed with a single + feature whose geometry is updated with this new polygon using the + `feature.setGeometry()` method. +--- + +import SolarTerminator from '../../components/examples/SolarTerminator.jsx'; + +