Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example showing the solar terminator #393

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions site/src/components/examples/SolarTerminator.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Map>
<View options={{center: [0, 0], zoom: 1}} />
<TileLayer>
<OSM />
</TileLayer>
<Layer style={{'fill-color': '#00000033'}}>
<Source options={{features: [feature]}} />
</Layer>
</Map>
<div style={{width: 150, position: 'absolute', top: 10, right: 10}}>
<b>{getDate(now.getUTCFullYear(), day, hour).toUTCString()}</b>
<hr />
<label>
Day of the year
<input
type="range"
min={0}
max={366}
style={{width: '100%'}}
value={day}
onChange={event => updateDay(parseInt(event.target.value))}
/>
</label>
<label>
Hour of the Day
<input
type="range"
min={0}
max={24}
step={1 / 60}
style={{width: '100%'}}
value={hour}
onChange={event => updateHour(parseFloat(event.target.value))}
/>
</label>
</div>
</>
);
}

export default SolarTerminator;
123 changes: 123 additions & 0 deletions site/src/components/examples/solar.js
Original file line number Diff line number Diff line change
@@ -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;
}
19 changes: 19 additions & 0 deletions site/src/content/examples/solar-terminator.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: 'Solar Terminator'
level: 2
description: |
This example shows how the `<Vector>` 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';

<SolarTerminator client:only="react" />