From 2505a0bdefdd0c33028f29aa5aabe450746f6784 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Sun, 11 Aug 2024 17:14:25 -0400 Subject: [PATCH 01/12] date format adjustments - setting up constants for iteration/testing - improve day calculations to avoid cross-midnight issues --- src/components/App.tsx | 2 +- src/constants.ts | 32 ++++++++++++++++++++++++++++++++ src/utils/daylight-utils.ts | 31 +++++++++++++++++-------------- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 6b7b03d..e96e560 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -70,7 +70,7 @@ export const App: React.FC = () => { const isResource = resource === `dataContextChangeNotice[${kDataContextName}]`; if (!isResource) return; - const casesDeleted = values.operation === "selectCases" && values.result.cases.length === 0 && values.result.success; + const casesDeleted = values.operation === "selectCases" && values.result.cases && values.result.cases.length === 0 && values.result.success; if ( casesDeleted ) { const uniqeLocations = await getUniqueLocationsRef.current(); diff --git a/src/constants.ts b/src/constants.ts index 3d65205..cd8d01c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -95,3 +95,35 @@ export const kChildCollectionAttributes = [ export const kDefaultOnAttributes = [ "date", "sunrise", "sunset", "dayLength" ]; + +// This is configurable because still have some development and iteration +// to do in terms of best fit for v3 and v2 use cases for this plugin. +export const kDateWithTimeFormats = { + + // (UTC) Do not use unless CODAP or plugin has mechanism to translate to a given TZ + // 1982-01-23T10:45Z + asZuluISO: "YYYY-MM-DDTHH:mm[Z]", + + // Use if both v2 and v3 will display clock time as expected, be okay with offset as programmatic asset + // 1982-01-23T10:45-07:00 (example offset -07:00) + asLocalISOWithTZOffset: "YYYY-MM-DDTHH:mmZ", // currently using this + + // Use if truly no offset is included/needed, local time only + // 1982-01-23T10:45 + asLocalISO: "YYYY-MM-DDTHH:mm", + + // use if we must display time as a string AND we have a CODAP way to treat As if needed to graph etc. + // 10:45 + asClockTimeString: "HH:mm", +} + +export const kDateFormats = { + + // Use if we need the real ISO date with year for graphing, etc. + // 1982-01-23 + asLocalISODate: "YYYY-MM-DD", // currently using this + + // Use if we must display date as a string AND we have a CODAP way to treat As if needed to graph etc. + // 01-23 + asCalendarDateString: "MM-DD", +} diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index 062416d..fabfea4 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -6,7 +6,7 @@ import tzlookup from "tz-lookup"; import { getSunrise, getSunset } from "sunrise-sunset-js"; import { Seasons } from "astronomy-engine"; import { DaylightInfo, DaylightCalcOptions, ILocation } from "../types"; -import { kBasicSummerSolstice, kEarthTilt } from "../constants"; +import { kBasicSummerSolstice, kDateFormats, kDateWithTimeFormats, kEarthTilt } from "../constants"; extend(utc); extend(dayOfYear); @@ -85,24 +85,27 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { const { latitude, longitude, year } = options; const results: DaylightInfo[] = []; - let currentDay = dayjs.utc(`${year}-01-01`); - const endOfYear = dayjs.utc(`${year + 1}-01-01`); + const timeZone = tzlookup(latitude, longitude); + let currentDay = dayjs.tz(`${year}-01-01`, timeZone).startOf("day"); + const endOfYear = dayjs.tz(`${year + 1}-01-01`, timeZone).startOf("day"); while (currentDay.isBefore(endOfYear)) { - const date = currentDay.toDate(); - const timeZone = tzlookup(latitude, longitude); + // Calculate for noon of the current day to ensure correct astronomical day + const noonDate = currentDay.hour(12).toDate(); - // TODO: handle above arctic circle and below antarctic circle - const utcSunrise = dayjs(getSunrise(latitude, longitude, date)); - const utcSunset = dayjs(getSunset(latitude, longitude, date)); - const tzSunrise = utcSunrise.tz(timeZone) - const tzSunset = utcSunset.tz(timeZone) + // Calculate sunrise and sunset in UTC + const utcSunrise = dayjs.utc(getSunrise(latitude, longitude, noonDate)); + const utcSunset = dayjs.utc(getSunset(latitude, longitude, noonDate)); + + // Convert to local time + const localSunrise = utcSunrise.tz(timeZone); + const localSunset = utcSunset.tz(timeZone); const record: DaylightInfo = { - day: currentDay.format("YYYY-MM-DD"), - sunrise: tzSunrise.format("YYYY-MM-DDTHH:mmZ"), - sunset: tzSunset.format("YYYY-MM-DDTHH:mmZ"), - dayLength: getDayLength(tzSunrise, tzSunset), + day: currentDay.format(kDateFormats.asLocalISODate), + sunrise: localSunrise.format(kDateWithTimeFormats.asLocalISOWithTZOffset), + sunset: localSunset.format(kDateWithTimeFormats.asLocalISOWithTZOffset), + dayLength: getDayLength(localSunrise, localSunset), dayAsInteger: currentDay.dayOfYear(), season: getSeasonName(currentDay, latitude), sunlightAngle: getSunrayAngleInDegrees(currentDay.dayOfYear(), kEarthTilt, latitude), From 696525c75c4613be8ef7f91effb956d216c9d8f7 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Mon, 12 Aug 2024 09:08:45 -0400 Subject: [PATCH 02/12] add fields for sunRiseMinSinceMidnight and sunSetMinSinceMidnight, to allow for meaningful graphs of sunrise and sunset times --- src/constants.ts | 12 ++++++++++++ src/hooks/useCodapData.ts | 4 +++- src/types.ts | 2 ++ src/utils/daylight-utils.ts | 9 ++++++++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index cd8d01c..196d916 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -89,6 +89,18 @@ export const kChildCollectionAttributes = [ title: "Season", type: "categorical", hasToken: true + }, + { + name: "sunriseMinSinceMidnight", + title: "Sunrise Minutes Since Midnight", + type: "numeric", + hasToken: true + }, + { + name: "sunsetMinSinceMidnight", + title: "Sunset Minutes Since Midnight", + type: "numeric", + hasToken: true } ]; diff --git a/src/hooks/useCodapData.ts b/src/hooks/useCodapData.ts index 8892c2e..46bc499 100644 --- a/src/hooks/useCodapData.ts +++ b/src/hooks/useCodapData.ts @@ -73,7 +73,9 @@ export const useCodapData = () => { dayLength: solarEvent.dayLength, season: solarEvent.season, sunlightAngle: solarEvent.sunlightAngle, - solarIntensity: solarEvent.solarIntensity + solarIntensity: solarEvent.solarIntensity, + sunriseMinSinceMidnight: solarEvent.sunriseMinSinceMidnight, + sunsetMinSinceMidnight: solarEvent.sunsetMinSinceMidnight }; return record; diff --git a/src/types.ts b/src/types.ts index 38215c9..ee16bf3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,6 +20,8 @@ export interface DaylightInfo { season: string; sunlightAngle: number; solarIntensity: number; + sunriseMinSinceMidnight: number; + sunsetMinSinceMidnight: number; } export interface GeoNameSearchOptions { diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index fabfea4..97ce719 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -81,6 +81,10 @@ export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:n return degrees; } +export function getMinutesSinceMidnight(time: Dayjs): number { + return time.hour() * 60 + time.minute(); +} + export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { const { latitude, longitude, year } = options; const results: DaylightInfo[] = []; @@ -109,8 +113,11 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { dayAsInteger: currentDay.dayOfYear(), season: getSeasonName(currentDay, latitude), sunlightAngle: getSunrayAngleInDegrees(currentDay.dayOfYear(), kEarthTilt, latitude), - solarIntensity: getSolarNoonIntensity(currentDay.dayOfYear(), latitude) + solarIntensity: getSolarNoonIntensity(currentDay.dayOfYear(), latitude), + sunriseMinSinceMidnight: getMinutesSinceMidnight(localSunrise), + sunsetMinSinceMidnight: getMinutesSinceMidnight(localSunset) }; + console.log("| record: ", record); results.push(record); currentDay = currentDay.add(1, "day"); } From 5f7e545ed3607444d80dc5ecccb2cc74807720a0 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Mon, 12 Aug 2024 10:39:04 -0400 Subject: [PATCH 03/12] date format adjustments - make all attributes show and disable hide/show for now (bug to fix) - make attributes for clock times categorical - set format to hh:mm a --- src/components/location-tab.tsx | 19 +++++++------- src/constants.ts | 44 +++++++++------------------------ src/types.ts | 2 +- src/utils/daylight-utils.ts | 6 ++--- 4 files changed, 25 insertions(+), 46 deletions(-) diff --git a/src/components/location-tab.tsx b/src/components/location-tab.tsx index 140b0f1..c086249 100644 --- a/src/components/location-tab.tsx +++ b/src/components/location-tab.tsx @@ -42,16 +42,17 @@ export const LocationTab: React.FC = ({ getUniqueLocationsInCodapData } = useCodapData(); - useEffect(() => { - const updateAttributesVisibility = async () => { - for (const attr of kChildCollectionAttributes) { - const isSelected = selectedAttrs.includes(attr.name); - await updateAttributeVisibility(attr.name, !isSelected); - } - }; + // TODO - this is causing re-render issues in v2, need to address attr visibility in a different way + // useEffect(() => { + // const updateAttributesVisibility = async () => { + // for (const attr of kChildCollectionAttributes) { + // const isSelected = selectedAttrs.includes(attr.name); + // await updateAttributeVisibility(attr.name, !isSelected); + // } + // }; - updateAttributesVisibility(); - }, [selectedAttrs, updateAttributeVisibility]); + // updateAttributesVisibility(); + // }, [selectedAttrs, updateAttributeVisibility]); const handleLatChange = (event: React.ChangeEvent) => { setLatitude(event.target.value); diff --git a/src/constants.ts b/src/constants.ts index 196d916..6b10a90 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -31,14 +31,11 @@ export const kParentCollectionAttributes = [ { name: "longitude", type: "numeric" - }, { name: "location", type: "categorical" - } - // NOTE: If data are to be historical, add year attribute ]; export const kChildCollectionAttributes = [ @@ -46,7 +43,8 @@ export const kChildCollectionAttributes = [ name: "date", title: "Date", type: "date", - hasToken: true + hasToken: true, + precision: "day" }, { name: "dayLength", @@ -57,20 +55,20 @@ export const kChildCollectionAttributes = [ { name: "sunrise", title: "Sunrise", - type: "date", + type: "categorical", hasToken: true }, { name: "sunset", title: "Sunset", - type: "date", + type: "categorical", hasToken: true }, { name: "dayNumber", title: "Day Number", type: "numeric", - hasToken: false + hasToken: true }, { name: "sunlightAngle", @@ -105,37 +103,17 @@ export const kChildCollectionAttributes = [ ]; export const kDefaultOnAttributes = [ - "date", "sunrise", "sunset", "dayLength" + "date", "sunrise", "sunset", "dayLength", "season", "sunlightAngle", "solarIntensity", "sunriseMinSinceMidnight", "sunsetMinSinceMidnight", "dayNumber" ]; -// This is configurable because still have some development and iteration -// to do in terms of best fit for v3 and v2 use cases for this plugin. export const kDateWithTimeFormats = { - - // (UTC) Do not use unless CODAP or plugin has mechanism to translate to a given TZ - // 1982-01-23T10:45Z - asZuluISO: "YYYY-MM-DDTHH:mm[Z]", - - // Use if both v2 and v3 will display clock time as expected, be okay with offset as programmatic asset - // 1982-01-23T10:45-07:00 (example offset -07:00) - asLocalISOWithTZOffset: "YYYY-MM-DDTHH:mmZ", // currently using this - - // Use if truly no offset is included/needed, local time only - // 1982-01-23T10:45 - asLocalISO: "YYYY-MM-DDTHH:mm", - - // use if we must display time as a string AND we have a CODAP way to treat As if needed to graph etc. - // 10:45 - asClockTimeString: "HH:mm", + asZuluISO: "YYYY-MM-DDTHH:mm[Z]", // 1999-01-23T21:45Z + asLocalISOWithTZOffset: "YYYY-MM-DDTHH:mmZ", // 1999-01-23T14:45-07:00 + asClockTimeString: "HH:mm", // 14:45 + asClockTimeStringAMPM: "h:mm a", // 2:45 PM } export const kDateFormats = { - - // Use if we need the real ISO date with year for graphing, etc. - // 1982-01-23 - asLocalISODate: "YYYY-MM-DD", // currently using this - - // Use if we must display date as a string AND we have a CODAP way to treat As if needed to graph etc. - // 01-23 + asLocalISODate: "YYYY-MM-DD", asCalendarDateString: "MM-DD", } diff --git a/src/types.ts b/src/types.ts index ee16bf3..73dbffa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,7 +12,7 @@ export interface DaylightCalcOptions { } export interface DaylightInfo { - day: string; + day: string; // read into CODAP as an ISO date sunrise: string; sunset: string; dayLength: number; diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index 97ce719..2fa9e52 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -107,8 +107,8 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { const record: DaylightInfo = { day: currentDay.format(kDateFormats.asLocalISODate), - sunrise: localSunrise.format(kDateWithTimeFormats.asLocalISOWithTZOffset), - sunset: localSunset.format(kDateWithTimeFormats.asLocalISOWithTZOffset), + sunrise: localSunrise.format(kDateWithTimeFormats.asClockTimeStringAMPM), + sunset: localSunset.format(kDateWithTimeFormats.asClockTimeStringAMPM), dayLength: getDayLength(localSunrise, localSunset), dayAsInteger: currentDay.dayOfYear(), season: getSeasonName(currentDay, latitude), @@ -117,7 +117,7 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { sunriseMinSinceMidnight: getMinutesSinceMidnight(localSunrise), sunsetMinSinceMidnight: getMinutesSinceMidnight(localSunset) }; - console.log("| record: ", record); + results.push(record); currentDay = currentDay.add(1, "day"); } From 2a64a34cd580a2acb37f14b463e82dd7f6c027ef Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Mon, 12 Aug 2024 11:11:32 -0400 Subject: [PATCH 04/12] update attribute visibility synchronously --- src/components/location-tab.tsx | 19 +++++++++---------- src/hooks/useCodapData.ts | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/location-tab.tsx b/src/components/location-tab.tsx index c086249..ccf2b6b 100644 --- a/src/components/location-tab.tsx +++ b/src/components/location-tab.tsx @@ -42,17 +42,16 @@ export const LocationTab: React.FC = ({ getUniqueLocationsInCodapData } = useCodapData(); - // TODO - this is causing re-render issues in v2, need to address attr visibility in a different way - // useEffect(() => { - // const updateAttributesVisibility = async () => { - // for (const attr of kChildCollectionAttributes) { - // const isSelected = selectedAttrs.includes(attr.name); - // await updateAttributeVisibility(attr.name, !isSelected); - // } - // }; + useEffect(() => { + const updateEachAttrVisibility = () => { + for (const attr of kChildCollectionAttributes) { + const isSelected = selectedAttrs.includes(attr.name); + updateAttributeVisibility(attr.name, !isSelected); + } + }; - // updateAttributesVisibility(); - // }, [selectedAttrs, updateAttributeVisibility]); + updateEachAttrVisibility(); + }, [selectedAttrs, updateAttributeVisibility]); const handleLatChange = (event: React.ChangeEvent) => { setLatitude(event.target.value); diff --git a/src/hooks/useCodapData.ts b/src/hooks/useCodapData.ts index 46bc499..7aecb24 100644 --- a/src/hooks/useCodapData.ts +++ b/src/hooks/useCodapData.ts @@ -86,11 +86,11 @@ export const useCodapData = () => { } }; - const updateAttributeVisibility = async (attributeName: string, hidden: boolean) => { + const updateAttributeVisibility = (attributeName: string, hidden: boolean) => { if (!dataContext) return; try { - await updateAttribute( + updateAttribute( kDataContextName, kChildCollectionName, attributeName, From 614b5a3d1367cda521c76d80f31e78e9e42382da Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Mon, 12 Aug 2024 11:28:05 -0400 Subject: [PATCH 05/12] improve cases deleted check by asserting cases exist --- src/components/App.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index e96e560..4bbb746 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -70,7 +70,11 @@ export const App: React.FC = () => { const isResource = resource === `dataContextChangeNotice[${kDataContextName}]`; if (!isResource) return; - const casesDeleted = values.operation === "selectCases" && values.result.cases && values.result.cases.length === 0 && values.result.success; + const casesDeleted = + values.operation === "selectCases" + && values.result.cases + && values.result.cases.length === 0 + && values.result.success; if ( casesDeleted ) { const uniqeLocations = await getUniqueLocationsRef.current(); From c6a6da234815fc1f70da5e8c6e6a88a4148953b2 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Mon, 12 Aug 2024 16:20:17 -0400 Subject: [PATCH 06/12] attempting to handle polar cases, partly working --- src/types.ts | 10 +++---- src/utils/daylight-utils.ts | 59 ++++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/types.ts b/src/types.ts index 73dbffa..e7543b9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,13 +13,13 @@ export interface DaylightCalcOptions { export interface DaylightInfo { day: string; // read into CODAP as an ISO date - sunrise: string; - sunset: string; - dayLength: number; + sunrise: string | null; + sunset: string | null; + dayLength?: number | null; dayAsInteger: number; season: string; - sunlightAngle: number; - solarIntensity: number; + sunlightAngle: number | null; + solarIntensity: number | null; sunriseMinSinceMidnight: number; sunsetMinSinceMidnight: number; } diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index 2fa9e52..6fcd084 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -57,7 +57,7 @@ function getSeasonName(dayJsDay: Dayjs, latitude: number): string { return season; } -export function getSolarNoonIntensity(dayNum: number, latitude: number): number { +export function getSolarNoonIntensity(dayNum: number, latitude: number): number | null { const solarConstant = 1361; const latitudeRad = latitude * Math.PI / 180; const declination = 23.45 * Math.sin((360/365) * (dayNum - 81) * Math.PI / 180); @@ -70,10 +70,10 @@ export function getSolarNoonIntensity(dayNum: number, latitude: number): number Math.cos(latitudeRad) * Math.cos(declinationRad); const solarNoonIntensity = solarConstant * eccentricityFactor * cosSolarZenithAngle; - return Math.max(0, solarNoonIntensity); // Ensure non-negative value + return solarNoonIntensity; } -export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:number): number { +export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:number): number | null { const tiltAxisZRadians = 2 * Math.PI * (dayNum - kBasicSummerSolstice) / 365; const orbitalTiltDegrees = earthTilt ? earthTilt : 0; const effectiveTiltDegrees = -Math.cos(tiltAxisZRadians) * orbitalTiltDegrees; @@ -82,6 +82,9 @@ export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:n } export function getMinutesSinceMidnight(time: Dayjs): number { + if (!time.isValid()) { + return 0; + } return time.hour() * 60 + time.minute(); } @@ -102,22 +105,56 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { const utcSunset = dayjs.utc(getSunset(latitude, longitude, noonDate)); // Convert to local time - const localSunrise = utcSunrise.tz(timeZone); - const localSunset = utcSunset.tz(timeZone); + const localSunriseObj = utcSunrise.tz(timeZone); + const localSunsetObj = utcSunset.tz(timeZone); + + const seasonName = getSeasonName(currentDay, latitude); + + const sunsetFormatted = localSunsetObj.format(kDateWithTimeFormats.asClockTimeStringAMPM); + const sunriseFormatted = localSunriseObj.format(kDateWithTimeFormats.asClockTimeStringAMPM); + + const validSunrise = typeof sunriseFormatted === "string" && sunriseFormatted !== "Invalid Date"; + const validSunset = typeof sunsetFormatted === "string" && sunsetFormatted !== "Invalid Date"; + + const startPolarSummer = validSunrise && !validSunset; + const startPolarWinter = !validSunrise && validSunset; + + const midPolarWinter = !validSunrise && !validSunset && (seasonName === "Winter" || seasonName === "Fall"); + const midPolarSummer = !validSunrise && !validSunset && (seasonName === "Summer" || seasonName === "Spring"); + + let finalDayLength = 0; + + if (midPolarSummer) { + finalDayLength = 24; + } + else if (midPolarWinter) { + finalDayLength = 0; + } + else if (startPolarSummer) { + finalDayLength = 24 - getMinutesSinceMidnight(localSunriseObj) / 60; + } + else if (startPolarWinter) { + finalDayLength = getMinutesSinceMidnight(localSunsetObj) / 60; + } + else { + finalDayLength = getDayLength(localSunriseObj, localSunsetObj); + } const record: DaylightInfo = { day: currentDay.format(kDateFormats.asLocalISODate), - sunrise: localSunrise.format(kDateWithTimeFormats.asClockTimeStringAMPM), - sunset: localSunset.format(kDateWithTimeFormats.asClockTimeStringAMPM), - dayLength: getDayLength(localSunrise, localSunset), + sunrise: validSunrise ? localSunriseObj.format(kDateWithTimeFormats.asClockTimeStringAMPM) : null, + sunset: validSunset ? localSunsetObj.format(kDateWithTimeFormats.asClockTimeStringAMPM) : null, + dayLength: finalDayLength, dayAsInteger: currentDay.dayOfYear(), - season: getSeasonName(currentDay, latitude), + season: seasonName, sunlightAngle: getSunrayAngleInDegrees(currentDay.dayOfYear(), kEarthTilt, latitude), solarIntensity: getSolarNoonIntensity(currentDay.dayOfYear(), latitude), - sunriseMinSinceMidnight: getMinutesSinceMidnight(localSunrise), - sunsetMinSinceMidnight: getMinutesSinceMidnight(localSunset) + sunriseMinSinceMidnight: getMinutesSinceMidnight(localSunriseObj), + sunsetMinSinceMidnight: getMinutesSinceMidnight(localSunsetObj) }; + console.log("| record: ", record); + results.push(record); currentDay = currentDay.add(1, "day"); } From 453ee9290d9d84ecf3057ba1c41806f713c9a3a0 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Mon, 12 Aug 2024 23:57:09 -0400 Subject: [PATCH 07/12] forcing utc fixes outliers that dont use dst --- src/types.ts | 4 ++-- src/utils/daylight-utils.ts | 36 +++++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/types.ts b/src/types.ts index e7543b9..a3626b4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,8 +20,8 @@ export interface DaylightInfo { season: string; sunlightAngle: number | null; solarIntensity: number | null; - sunriseMinSinceMidnight: number; - sunsetMinSinceMidnight: number; + sunriseMinSinceMidnight: number | null; + sunsetMinSinceMidnight: number | null; } export interface GeoNameSearchOptions { diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index 6fcd084..9ab5947 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -12,12 +12,28 @@ extend(utc); extend(dayOfYear); extend(timezone); + function getDayLength(sunrise: Dayjs, sunset: Dayjs): number { - const utcMidnight = sunrise.startOf("day"); - const utcSunriseSinceMidnight = sunrise.diff(utcMidnight, "hour", true); - const utcSunsetSinceMidnight = sunset.diff(utcMidnight, "hour", true); - let dayLength = utcSunsetSinceMidnight - utcSunriseSinceMidnight; - return dayLength < 0 ? dayLength + 24 : dayLength; + const utcSunrise = sunrise.utc(true); + const utcSunset = sunset.utc(true); + + const utcMidnight = utcSunrise.startOf("day"); + + const utcSunriseSinceMidnight = utcSunrise.diff(utcMidnight, "hour", true); + const utcSunsetSinceMidnight = utcSunset.diff(utcMidnight, "hour", true); + + const initialDayLength = utcSunsetSinceMidnight - utcSunriseSinceMidnight; + const dayLength = initialDayLength < 0 ? initialDayLength + 24 : initialDayLength; + + return dayLength; +} + +function hasDST(latitude: number, longitude: number, year: number): boolean { + const timeZone = tzlookup(latitude, longitude); + const winterDate = dayjs.tz(`${year}-01-01`, timeZone); + const summerDate = dayjs.tz(`${year}-07-01`, timeZone); + + return winterDate.utcOffset() !== summerDate.utcOffset(); } function getSeasonName(dayJsDay: Dayjs, latitude: number): string { @@ -104,7 +120,6 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { const utcSunrise = dayjs.utc(getSunrise(latitude, longitude, noonDate)); const utcSunset = dayjs.utc(getSunset(latitude, longitude, noonDate)); - // Convert to local time const localSunriseObj = utcSunrise.tz(timeZone); const localSunsetObj = utcSunset.tz(timeZone); @@ -116,6 +131,7 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { const validSunrise = typeof sunriseFormatted === "string" && sunriseFormatted !== "Invalid Date"; const validSunset = typeof sunsetFormatted === "string" && sunsetFormatted !== "Invalid Date"; + // Transition days for midnight sun or polar night periods const startPolarSummer = validSunrise && !validSunset; const startPolarWinter = !validSunrise && validSunset; @@ -138,6 +154,11 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { } else { finalDayLength = getDayLength(localSunriseObj, localSunsetObj); + const tzHasDST = hasDST(latitude, longitude, year); + const isSpringForward = localSunriseObj.utcOffset() < localSunsetObj.utcOffset(); + if (isSpringForward && tzHasDST) { + finalDayLength -= 1; // Subtract the extra hour added due to DST + } } const record: DaylightInfo = { @@ -152,9 +173,6 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { sunriseMinSinceMidnight: getMinutesSinceMidnight(localSunriseObj), sunsetMinSinceMidnight: getMinutesSinceMidnight(localSunsetObj) }; - - console.log("| record: ", record); - results.push(record); currentDay = currentDay.add(1, "day"); } From 8ef25d5ce175df468635be1030879d17cd5d5ba8 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Tue, 13 Aug 2024 00:23:15 -0400 Subject: [PATCH 08/12] fix transition day outliers - if we spring forward, we lose an hour - it will come out of day length if transition happens after sunrise, in UTC - so on transition days like this we can adjust to remove outlier - but this is commented out for now --- src/utils/daylight-utils.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index 9ab5947..abd6c7a 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -154,11 +154,13 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { } else { finalDayLength = getDayLength(localSunriseObj, localSunsetObj); - const tzHasDST = hasDST(latitude, longitude, year); - const isSpringForward = localSunriseObj.utcOffset() < localSunsetObj.utcOffset(); - if (isSpringForward && tzHasDST) { - finalDayLength -= 1; // Subtract the extra hour added due to DST - } + // uncomment to pretend DST spring forward does not lose us an hour one day of year + // removing the outlier in places where DST transition happens post sunrise + // const tzHasDST = hasDST(latitude, longitude, year); + // const isSpringForward = localSunriseObj.utcOffset() < localSunsetObj.utcOffset(); + // if (isSpringForward && tzHasDST) { + // finalDayLength -= 1; // Subtract the extra hour added due to DST + // } } const record: DaylightInfo = { From 45c9c17653777bb66f1555eb2a0802df8e02aac4 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Tue, 13 Aug 2024 00:43:53 -0400 Subject: [PATCH 09/12] proof-of-concept - add a formula to an attribute --- src/constants.ts | 7 +++++++ src/utils/daylight-utils.ts | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 6b10a90..083cee3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -46,6 +46,13 @@ export const kChildCollectionAttributes = [ hasToken: true, precision: "day" }, + { + name: "monthDay", + title: "Day of Month", + type: "categorical", + hasToken: true, + formula: "monthName(date) + ' ' + dayOfMonth(date)" + }, { name: "dayLength", title: "Day Length", diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index abd6c7a..d8a1c52 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -79,9 +79,7 @@ export function getSolarNoonIntensity(dayNum: number, latitude: number): number const declination = 23.45 * Math.sin((360/365) * (dayNum - 81) * Math.PI / 180); const declinationRad = declination * Math.PI / 180; const dayAngle = 2 * Math.PI * (dayNum - 1) / 365; - // correction factor for Earth's elliptical orbit - const eccentricityFactor = 1 + 0.033 * Math.cos(dayAngle); - // cosine of the solar zenith angle at solar noon + const eccentricityFactor = 1 + 0.033 * Math.cos(dayAngle); // correction factor for Earth's elliptical orbit const cosSolarZenithAngle = Math.sin(latitudeRad) * Math.sin(declinationRad) + Math.cos(latitudeRad) * Math.cos(declinationRad); From 30b152a792d66203f0cce38a23f712330a5e9f37 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Tue, 13 Aug 2024 10:07:00 -0400 Subject: [PATCH 10/12] cleanup warnings --- src/components/App.tsx | 31 ++++++++++++++++--------------- src/utils/daylight-utils.ts | 17 +++++++++-------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 4bbb746..b41c318 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -19,7 +19,7 @@ export const App: React.FC = () => { const [activeTab, setActiveTab] = useState<"location" | "simulation">("location"); const [latitude, setLatitude] = useState(""); const [longitude, setLongitude] = useState(""); - const [dayOfYear, setDayOfYear] = useState(171); + const [dayOfYear, /*setDayOfYear */] = useState(171); const [locations, setLocations] = useState([]); const [locationSearch, setLocationSearch] = useState(""); const [selectedAttrs, setSelectedAttributes] = useState(kDefaultOnAttributes); @@ -34,20 +34,21 @@ export const App: React.FC = () => { // of the sync process, when user select a row in CODAP and we want to update the day in the simulation tab. }; - const handleCaseSelectionInCodap = (_latitude: string, _longitude: string, day: number) => { - // Option 1. Update as much of the plugin state as we can when user selects a case in CODAP. I think this might - // be too much, as it'll clear all the inputs in all the tabs and the user will have to re-enter everything - // if they were in the middle of something. - // setDayOfYear(day); - // setLatitude(_latitude); - // setLongitude(_longitude); - // ...OR... - // Option 2. Update only the day of the year, as that's reasonably unobtrusive and useful. We can first check - // if user actually selected the case from the same location, and only then update the day of the year. - if (latitude === _latitude && longitude === _longitude) { - setDayOfYear(day); - } - } + // TODO: Handle case selection - sync sim tab with CODAP selection + // const handleCaseSelectionInCodap = (_latitude: string, _longitude: string, day: number) => { + // // Option 1. Update as much of the plugin state as we can when user selects a case in CODAP. I think this might + // // be too much, as it'll clear all the inputs in all the tabs and the user will have to re-enter everything + // // if they were in the middle of something. + // // setDayOfYear(day); + // // setLatitude(_latitude); + // // setLongitude(_longitude); + // // ...OR... + // // Option 2. Update only the day of the year, as that's reasonably unobtrusive and useful. We can first check + // // if user actually selected the case from the same location, and only then update the day of the year. + // if (latitude === _latitude && longitude === _longitude) { + // setDayOfYear(day); + // } + // } const { getUniqueLocationsInCodapData } = useCodapData(); // Store a ref to getUniqueLocationsInCodapData so we can call inside useEffect without triggering unnecessary re-runs diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index d8a1c52..c13ec71 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -28,13 +28,14 @@ function getDayLength(sunrise: Dayjs, sunset: Dayjs): number { return dayLength; } -function hasDST(latitude: number, longitude: number, year: number): boolean { - const timeZone = tzlookup(latitude, longitude); - const winterDate = dayjs.tz(`${year}-01-01`, timeZone); - const summerDate = dayjs.tz(`${year}-07-01`, timeZone); +// Uncomment this to use in the DST adjustment commented out below +// function hasDST(latitude: number, longitude: number, year: number): boolean { +// const timeZone = tzlookup(latitude, longitude); +// const winterDate = dayjs.tz(`${year}-01-01`, timeZone); +// const summerDate = dayjs.tz(`${year}-07-01`, timeZone); - return winterDate.utcOffset() !== summerDate.utcOffset(); -} +// return winterDate.utcOffset() !== summerDate.utcOffset(); +// } function getSeasonName(dayJsDay: Dayjs, latitude: number): string { const year = dayJsDay.year(); @@ -73,7 +74,7 @@ function getSeasonName(dayJsDay: Dayjs, latitude: number): string { return season; } -export function getSolarNoonIntensity(dayNum: number, latitude: number): number | null { +export function getSolarNoonIntensity(dayNum: number, latitude: number): number { const solarConstant = 1361; const latitudeRad = latitude * Math.PI / 180; const declination = 23.45 * Math.sin((360/365) * (dayNum - 81) * Math.PI / 180); @@ -87,7 +88,7 @@ export function getSolarNoonIntensity(dayNum: number, latitude: number): number return solarNoonIntensity; } -export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:number): number | null { +export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:number): number { const tiltAxisZRadians = 2 * Math.PI * (dayNum - kBasicSummerSolstice) / 365; const orbitalTiltDegrees = earthTilt ? earthTilt : 0; const effectiveTiltDegrees = -Math.cos(tiltAxisZRadians) * orbitalTiltDegrees; From 675c6ece2159c01492245656334da2740561070e Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Wed, 14 Aug 2024 12:33:40 -0400 Subject: [PATCH 11/12] using formulas for Sunrise, Sunset, cleaning up and adding strings for other attributes --- src/constants.ts | 89 ++++++++++++++++++++----------------- src/hooks/useCodapData.ts | 15 +++---- src/types.ts | 15 +++---- src/utils/daylight-utils.ts | 60 +++++++++---------------- 4 files changed, 81 insertions(+), 98 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 083cee3..32672b6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -23,6 +23,8 @@ export const kDefaultMaxRows = 4; export const kParentCollectionName = "Locations"; export const kChildCollectionName = "Daylight Info"; +export const kAdjustSpringForwardOutlier = false; + export const kParentCollectionAttributes = [ { name: "latitude", @@ -44,73 +46,79 @@ export const kChildCollectionAttributes = [ title: "Date", type: "date", hasToken: true, - precision: "day" + precision: "day", + description: "Date" }, { - name: "monthDay", - title: "Day of Month", - type: "categorical", + name: "Day length", + title: "Day length", + type: "numeric", hasToken: true, - formula: "monthName(date) + ' ' + dayOfMonth(date)" + unit: "hours", + description: "Day length in hours" }, { - name: "dayLength", - title: "Day Length", - type: "numeric", - hasToken: true + name: "rawSunrise", + title: "rawSunrise", + type: "date", + hasToken: false, + hidden: true, + precision: "seconds", + description: "sunrise as date object" }, { - name: "sunrise", - title: "Sunrise", - type: "categorical", - hasToken: true + name: "rawSunset", + title: "rawSunset", + type: "date", + hasToken: false, + hidden: true, + precision: "seconds", + description: "sunset as date object" }, { - name: "sunset", - title: "Sunset", - type: "categorical", - hasToken: true + name: "Sunrise", + title: "Sunrise", + type: "numeric", + hasToken: true, + unit: "decimal hours", + formula: "hours(rawSunrise)+minutes(rawSunrise)/60", + description: "time in decimal hours" }, { - name: "dayNumber", - title: "Day Number", + name: "Sunset", + title: "Sunset", type: "numeric", - hasToken: true + hasToken: true, + unit: "decimal hours", + formula: "hours(rawSunset)+minutes(rawSunset)/60", + description: "time in decimal hours" }, { - name: "sunlightAngle", - title: "Sunlight Angle", + name: "Sunlight angle", + title: "Sunlight angle", type: "numeric", - hasToken: true + hasToken: true, + unit: "°", + description: "angle in degrees of sunlight at solar noon" }, { - name: "solarIntensity", - title: "Solar Intensity", + name: "Solar intensity", + title: "Solar intensity", type: "numeric", - hasToken: true + hasToken: true, + unit: "W/㎡", + description: "intensity of solar energy in watts per square meter at solar noon, disregarding all atmospheric effects" }, { - name: "season", + name: "Season", title: "Season", type: "categorical", hasToken: true - }, - { - name: "sunriseMinSinceMidnight", - title: "Sunrise Minutes Since Midnight", - type: "numeric", - hasToken: true - }, - { - name: "sunsetMinSinceMidnight", - title: "Sunset Minutes Since Midnight", - type: "numeric", - hasToken: true } ]; export const kDefaultOnAttributes = [ - "date", "sunrise", "sunset", "dayLength", "season", "sunlightAngle", "solarIntensity", "sunriseMinSinceMidnight", "sunsetMinSinceMidnight", "dayNumber" + "date", "Day length" ]; export const kDateWithTimeFormats = { @@ -122,5 +130,4 @@ export const kDateWithTimeFormats = { export const kDateFormats = { asLocalISODate: "YYYY-MM-DD", - asCalendarDateString: "MM-DD", } diff --git a/src/hooks/useCodapData.ts b/src/hooks/useCodapData.ts index 7aecb24..7ac7d0a 100644 --- a/src/hooks/useCodapData.ts +++ b/src/hooks/useCodapData.ts @@ -66,16 +66,13 @@ export const useCodapData = () => { latitude: location.latitude, longitude: location.longitude, location: location.name, - dayNumber: solarEvent.dayAsInteger, date: solarEvent.day, - sunrise: solarEvent.sunrise, - sunset: solarEvent.sunset, - dayLength: solarEvent.dayLength, - season: solarEvent.season, - sunlightAngle: solarEvent.sunlightAngle, - solarIntensity: solarEvent.solarIntensity, - sunriseMinSinceMidnight: solarEvent.sunriseMinSinceMidnight, - sunsetMinSinceMidnight: solarEvent.sunsetMinSinceMidnight + rawSunrise: solarEvent.rawSunrise, + rawSunset: solarEvent.rawSunset, + "Day length": solarEvent.dayLength, + "Season": solarEvent.season, + "Sunlight angle": solarEvent.sunlightAngle, + "Solar intensity": solarEvent.solarIntensity }; return record; diff --git a/src/types.ts b/src/types.ts index a3626b4..ad49c82 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,16 +12,13 @@ export interface DaylightCalcOptions { } export interface DaylightInfo { - day: string; // read into CODAP as an ISO date - sunrise: string | null; - sunset: string | null; - dayLength?: number | null; - dayAsInteger: number; + day: string; // read into CODAP as an ISO date + rawSunrise: string; // read into CODAP as an ISO date + rawSunset: string; // read into CODAP as an ISO date + dayLength: number; season: string; - sunlightAngle: number | null; - solarIntensity: number | null; - sunriseMinSinceMidnight: number | null; - sunsetMinSinceMidnight: number | null; + sunlightAngle: number; + solarIntensity: number; } export interface GeoNameSearchOptions { diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index c13ec71..270b714 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -6,7 +6,7 @@ import tzlookup from "tz-lookup"; import { getSunrise, getSunset } from "sunrise-sunset-js"; import { Seasons } from "astronomy-engine"; import { DaylightInfo, DaylightCalcOptions, ILocation } from "../types"; -import { kBasicSummerSolstice, kDateFormats, kDateWithTimeFormats, kEarthTilt } from "../constants"; +import { kBasicSummerSolstice, kDateFormats, kDateWithTimeFormats, kEarthTilt, kAdjustSpringForwardOutlier } from "../constants"; extend(utc); extend(dayOfYear); @@ -28,14 +28,13 @@ function getDayLength(sunrise: Dayjs, sunset: Dayjs): number { return dayLength; } -// Uncomment this to use in the DST adjustment commented out below -// function hasDST(latitude: number, longitude: number, year: number): boolean { -// const timeZone = tzlookup(latitude, longitude); -// const winterDate = dayjs.tz(`${year}-01-01`, timeZone); -// const summerDate = dayjs.tz(`${year}-07-01`, timeZone); +function hasDST(latitude: number, longitude: number, year: number): boolean { + const timeZone = tzlookup(latitude, longitude); + const winterDate = dayjs.tz(`${year}-01-01`, timeZone); + const summerDate = dayjs.tz(`${year}-07-01`, timeZone); -// return winterDate.utcOffset() !== summerDate.utcOffset(); -// } + return winterDate.utcOffset() !== summerDate.utcOffset(); +} function getSeasonName(dayJsDay: Dayjs, latitude: number): string { const year = dayJsDay.year(); @@ -112,30 +111,19 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { const endOfYear = dayjs.tz(`${year + 1}-01-01`, timeZone).startOf("day"); while (currentDay.isBefore(endOfYear)) { - // Calculate for noon of the current day to ensure correct astronomical day const noonDate = currentDay.hour(12).toDate(); - - // Calculate sunrise and sunset in UTC const utcSunrise = dayjs.utc(getSunrise(latitude, longitude, noonDate)); const utcSunset = dayjs.utc(getSunset(latitude, longitude, noonDate)); - + const seasonName = getSeasonName(currentDay, latitude); const localSunriseObj = utcSunrise.tz(timeZone); const localSunsetObj = utcSunset.tz(timeZone); - const seasonName = getSeasonName(currentDay, latitude); - - const sunsetFormatted = localSunsetObj.format(kDateWithTimeFormats.asClockTimeStringAMPM); - const sunriseFormatted = localSunriseObj.format(kDateWithTimeFormats.asClockTimeStringAMPM); - - const validSunrise = typeof sunriseFormatted === "string" && sunriseFormatted !== "Invalid Date"; - const validSunset = typeof sunsetFormatted === "string" && sunsetFormatted !== "Invalid Date"; - // Transition days for midnight sun or polar night periods - const startPolarSummer = validSunrise && !validSunset; - const startPolarWinter = !validSunrise && validSunset; - - const midPolarWinter = !validSunrise && !validSunset && (seasonName === "Winter" || seasonName === "Fall"); - const midPolarSummer = !validSunrise && !validSunset && (seasonName === "Summer" || seasonName === "Spring"); + const startPolarSummer = localSunriseObj.isValid() && !localSunsetObj.isValid(); + const startPolarWinter = !localSunriseObj.isValid() && localSunsetObj.isValid(); + const midPolar = !localSunriseObj.isValid() && !localSunsetObj.isValid(); + const midPolarWinter = midPolar && (seasonName === "Winter" || seasonName === "Fall"); + const midPolarSummer = midPolar && (seasonName === "Summer" || seasonName === "Spring"); let finalDayLength = 0; @@ -153,31 +141,25 @@ export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] { } else { finalDayLength = getDayLength(localSunriseObj, localSunsetObj); - // uncomment to pretend DST spring forward does not lose us an hour one day of year - // removing the outlier in places where DST transition happens post sunrise - // const tzHasDST = hasDST(latitude, longitude, year); - // const isSpringForward = localSunriseObj.utcOffset() < localSunsetObj.utcOffset(); - // if (isSpringForward && tzHasDST) { - // finalDayLength -= 1; // Subtract the extra hour added due to DST - // } + // Optional adjustment of outlier on spring forward day for some timezones + if (kAdjustSpringForwardOutlier){ + const isSpringForward = localSunriseObj.utcOffset() < localSunsetObj.utcOffset(); + if (hasDST(latitude, longitude, year) && isSpringForward) finalDayLength -= 1; + } } const record: DaylightInfo = { day: currentDay.format(kDateFormats.asLocalISODate), - sunrise: validSunrise ? localSunriseObj.format(kDateWithTimeFormats.asClockTimeStringAMPM) : null, - sunset: validSunset ? localSunsetObj.format(kDateWithTimeFormats.asClockTimeStringAMPM) : null, + rawSunrise: localSunriseObj.format(kDateWithTimeFormats.asLocalISOWithTZOffset), + rawSunset: localSunsetObj.format(kDateWithTimeFormats.asLocalISOWithTZOffset), dayLength: finalDayLength, - dayAsInteger: currentDay.dayOfYear(), season: seasonName, sunlightAngle: getSunrayAngleInDegrees(currentDay.dayOfYear(), kEarthTilt, latitude), - solarIntensity: getSolarNoonIntensity(currentDay.dayOfYear(), latitude), - sunriseMinSinceMidnight: getMinutesSinceMidnight(localSunriseObj), - sunsetMinSinceMidnight: getMinutesSinceMidnight(localSunsetObj) + solarIntensity: getSolarNoonIntensity(currentDay.dayOfYear(), latitude) }; results.push(record); currentDay = currentDay.add(1, "day"); } - return results; } From 96669427fc93070c7d62b3e5713ad75433b65db4 Mon Sep 17 00:00:00 2001 From: Joe Bacal Date: Wed, 14 Aug 2024 13:52:49 -0400 Subject: [PATCH 12/12] set floor of solar intensity to 0 --- src/components/App.tsx | 2 +- src/utils/daylight-utils.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index b41c318..e4e7ed4 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -26,7 +26,7 @@ export const App: React.FC = () => { const [dataContext, setDataContext] = useState(null); const handleDayUpdateInTheSimTab = (day: number) => { - console.log("The day of the year has been updated in the simulation tab to: ", day); // TODO: remove it later + // console.log("The day of the year has been updated in the simulation tab to: ", day); // TODO: implement this // We might to debounce this call, as if the animation is on, or user is dragging the slider, there will be // lot of events and API calls to CODAP. updateRowSelectionInCodap(latitude, longitude, Math.floor(day)); diff --git a/src/utils/daylight-utils.ts b/src/utils/daylight-utils.ts index 270b714..6de5d5a 100644 --- a/src/utils/daylight-utils.ts +++ b/src/utils/daylight-utils.ts @@ -84,7 +84,14 @@ export function getSolarNoonIntensity(dayNum: number, latitude: number): number Math.cos(latitudeRad) * Math.cos(declinationRad); const solarNoonIntensity = solarConstant * eccentricityFactor * cosSolarZenithAngle; - return solarNoonIntensity; + + // Note: This calculation returns theoretical intensity at the top of the atmosphere. + // Negative values are clamped to zero, which + // represents times when the sun is below the horizon + // In reality, some diffuse light might still be present due to atmospheric scattering. + // None of the calculations in this plugin use "civil twighlight" or associated definitions + // For day length either, so this is consistent with the rest of the calculations. + return Math.max(0, solarNoonIntensity); } export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:number): number { @@ -96,10 +103,7 @@ export function getSunrayAngleInDegrees(dayNum: number, earthTilt: number, lat:n } export function getMinutesSinceMidnight(time: Dayjs): number { - if (!time.isValid()) { - return 0; - } - return time.hour() * 60 + time.minute(); + return !time.isValid() ? 0 : time.hour() * 60 + time.minute(); } export function getDayLightInfo(options: DaylightCalcOptions): DaylightInfo[] {