Solar Terminator

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 Feature from 'ol/Feature.js';
import Layer from '@planet/maps/layer/Vector.js';
import Map from '@planet/maps/Map.js';
import OSM from '@planet/maps/source/OSM.js';
import React, {useEffect, useState} from 'react';
import Source from '@planet/maps/source/Vector.js';
import TileLayer from '@planet/maps/layer/WebGLTile.js';
import View from '@planet/maps/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;