@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
242 lines (241 loc) • 10.9 kB
JavaScript
import { during } from 'ol/format/filter.js';
export const TIME_RANGE_SEPARATOR = '/';
// In ISO 8601 format
const MIN_DATE = '1900-01-01T00:00:00.000Z';
const MAX_DATE = '2100-01-01T00:00:00.000Z';
/**
* A class for formatting date and time values based on an `ITimeOption` object.
* Provides utility functions for parsing dates and date ranges, validating them,
* and generating formatted query strings for temporal layer filtering.
*
* The class allows customized configurations through its constructor options, including setting
* minimum and maximum date bounds, default values, resolution levels, and operation modes.
*/
class LayerTimeFormatter {
minValue;
maxValue;
resolution;
mode;
minDefaultValue;
maxDefaultValue;
constructor(options) {
this.minValue = new Date(options?.minValue ?? MIN_DATE);
this.maxValue = new Date(options?.maxValue ?? MAX_DATE);
this.resolution = options?.resolution ?? 'month';
this.mode = options?.mode ?? 'range';
this.minDefaultValue = options?.minDefValue ? new Date(options?.minDefValue) : undefined;
this.maxDefaultValue = options?.maxDefValue ? new Date(options?.maxDefValue) : undefined;
}
/**
* Receives a date string or date object, validates it against the specified time options,
* and generates a formatted query string for usage as URL query parameter.
*
* @param {string|Date} date - The date-like string or date object to be parsed and converted.
* @return {string} A formatted query string, or an empty string if the date is invalid.
*/
formatDateString(date) {
let dateObject;
if (typeof date === 'string') {
dateObject = this.parseDateString(date);
}
else {
dateObject = date;
}
if (dateObject && this.isWithinRange(dateObject)) {
return LayerTimeFormatter.queryStringFromSingleValueAndResolution(dateObject, this.resolution);
}
return '';
}
/**
* Generates a formatted query string from a provided date range for usage as URL query parameter.
* The provided dates are shortened to fit the defined resolution.
*
* @param lowerLimit The lower bound of the time range as a date-like string or date object.
* @param upperLimit The upper bound of the time range as a date-like string or date object.
* @return A formatted query string, or an empty string if the dates are invalid.
*/
formatTimeRange(lowerLimit, upperLimit) {
if (this.mode === 'range') {
let lowerDate;
let upperDate;
if (typeof lowerLimit === 'string') {
lowerDate = this.parseDateString(lowerLimit);
}
else {
lowerDate = lowerLimit;
}
if (typeof upperLimit === 'string') {
upperDate = this.parseDateString(upperLimit);
}
else {
upperDate = upperLimit;
}
if (lowerDate && this.isWithinRange(lowerDate) && upperDate && this.isWithinRange(upperDate)) {
return LayerTimeFormatter.queryStringFromDateRangeAnResolution(lowerDate, upperDate, this.resolution);
}
}
return '';
}
/**
* Simple date transform method from a date-like string to a date object without validating it against the time options.
*
* @param {string} dateStr - The date string to be parsed.
* @return {Date | undefined} The resulting Date object if the input is valid, otherwise undefined.
*/
parseDateString(dateStr) {
if (!this.isValidDateString(dateStr))
return undefined;
return new Date(dateStr);
}
/**
* Helper method to quickly generate a formated query string from the default time restriction
* provided by the time options.
*
* @return {string | undefined} The formatted defaults as query string or undefined if no defaults are available.
*/
getFormattedDefault() {
if (this.mode === 'range' && this.minDefaultValue && this.maxDefaultValue) {
return this.formatTimeRange(this.minDefaultValue, this.maxDefaultValue);
}
if (this.mode === 'value' && this.minDefaultValue) {
return this.formatDateString(this.minDefaultValue);
}
return undefined;
}
/**
* Converts the WMS temporal query string into an OpenLayers WFS filter.
*
* @param {string} timeRestriction - The time restriction query string used for the WMS request, e.g. '2020-02', '2012-11-04/2012-11-14'.
* @param {string} timeAttribute - The name of the attribute that is used for temporal filtering.
* @return {Filter | undefined} Returns a new OpenLayers WFS filter object, or undefined if no valid filter could be created.
*/
toOpenLayersWfsFilter(timeRestriction, timeAttribute) {
let lower = timeRestriction.split('/')[0];
let upper = timeRestriction.split('/')[1] ?? undefined;
let temporalFilter = undefined;
if (this.mode === 'range') {
temporalFilter = during(timeAttribute, lower, upper);
}
else {
const date = this.parseDateString(timeRestriction);
if (!date)
return temporalFilter;
// Based on the specified resolution, create a dateTime range in UTC time as ISO strings
switch (this.resolution) {
case 'day':
lower = date.toISOString();
date.setUTCDate(date.getUTCDay() + 1);
date.setUTCMilliseconds(-1);
upper = date.toISOString();
break;
case 'week':
lower = LayerTimeFormatter.getMondayOfWeek(date).toISOString();
upper = LayerTimeFormatter.getSundayOfWeek(date).toISOString();
break;
case 'month':
lower = date.toISOString();
date.setUTCMonth(date.getUTCMonth() + 1);
date.setUTCMilliseconds(-1);
upper = date.toISOString();
break;
case 'year':
lower = date.toISOString();
date.setUTCFullYear(date.getUTCFullYear() + 1);
date.setUTCMilliseconds(-1);
upper = date.toISOString();
break;
default:
return temporalFilter;
}
temporalFilter = during(timeAttribute, lower, upper);
}
return temporalFilter;
}
isValidDateString(dateString) {
return !isNaN(Date.parse(dateString));
}
isWithinRange(date) {
return date >= this.minValue && date <= this.maxValue;
}
/**
* Formats a given date into a date string of type 'YYYY-MM-DD'.
*/
static formatAsDate(date) {
return date.toISOString().split('T')[0];
}
static getMondayOfWeek(date) {
const monday = date.getUTCDate() - date.getUTCDay() + (date.getUTCDay() === 0 ? -6 : 1);
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), monday));
}
static getSundayOfWeek(date) {
const weekEndDay = date.getUTCDate() - (date.getUTCDay() === 0 ? 7 : date.getUTCDay()) + 7;
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), weekEndDay));
}
/**
* Formats a given date into a string, representing the week range from Monday to Sunday that the date falls into,
* e.g. 'YYYY-MM-DD/YYYY-MM-DD'
*/
static formatAsWeekRange(date) {
const startOfWeek = LayerTimeFormatter.getMondayOfWeek(date);
const endOfWeek = LayerTimeFormatter.getSundayOfWeek(date);
return `${LayerTimeFormatter.formatAsDate(startOfWeek)}${TIME_RANGE_SEPARATOR}${LayerTimeFormatter.formatAsDate(endOfWeek)}`;
}
/**
* Formats a given date into a date string of type 'YYYY-MM'.
*/
static formatAsMonth(date) {
return `${date.toISOString().split('T')[0].slice(0, -3)}`;
}
/**
* Formats a given date into a year string of type 'YYYY'.
*/
static formatAsYear(date) {
return `${date.getUTCFullYear()}`;
}
/**
* Constructs a query string based on a single date value and a specified time resolution.
*
* @param {Date} date - The date value to be formatted.
* @param {TimeResolution} resolution - The time resolution specifying the format (e.g. day, week, month, year).
* @return {string} A formatted string representation of the date based on the provided resolution.
*/
static queryStringFromSingleValueAndResolution(date, resolution) {
switch (resolution) {
case 'day':
return LayerTimeFormatter.formatAsDate(date);
case 'week':
// This will return a range, even when in 'value' mode!
return LayerTimeFormatter.formatAsWeekRange(date);
case 'month':
return LayerTimeFormatter.formatAsMonth(date);
case 'year':
return LayerTimeFormatter.formatAsYear(date);
default:
return date.toISOString();
}
}
/**
* Formats a date range as a string, separated by a forward slash and taking the resolution into account
* when defining the precision of the date strings.
*
* @param {Date} lowerLimit - The starting date of the range.
* @param {Date} upperLimit - The ending date of the range.
* @param {TimeResolution} resolution - The resolution of the date range, can be 'day', 'week', 'month', or 'year'.
* @return {string} A query string representing the date range formatted according to the specified resolution.
*/
static queryStringFromDateRangeAnResolution(lowerLimit, upperLimit, resolution) {
switch (resolution) {
case 'day':
return `${LayerTimeFormatter.formatAsDate(lowerLimit)}${TIME_RANGE_SEPARATOR}${LayerTimeFormatter.formatAsDate(upperLimit)}`;
case 'week':
return `${LayerTimeFormatter.formatAsDate(LayerTimeFormatter.getMondayOfWeek(lowerLimit))}/${LayerTimeFormatter.formatAsDate(LayerTimeFormatter.getSundayOfWeek(upperLimit))}`;
case 'month':
return `${LayerTimeFormatter.formatAsMonth(lowerLimit)}${TIME_RANGE_SEPARATOR}${LayerTimeFormatter.formatAsMonth(upperLimit)}`;
case 'year':
return `${LayerTimeFormatter.formatAsYear(lowerLimit)}${TIME_RANGE_SEPARATOR}${LayerTimeFormatter.formatAsYear(upperLimit)}`;
default:
return `${lowerLimit.toISOString()}${TIME_RANGE_SEPARATOR}${upperLimit.toISOString()}`;
}
}
}
export default LayerTimeFormatter;