Skip to content

Commit

Permalink
Factor lat/long into locations and sun calcs
Browse files Browse the repository at this point in the history
  • Loading branch information
toddmedema committed Apr 13, 2024
1 parent 617ae4d commit 7cf8cbf
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 134 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"react-scripts": "5.0.1",
"react-transition-group": "^4.4.5",
"sass": "^1.72.0",
"suncalc": "^1.9.0",
"typescript": "^4.9.5",
"victory": "^37.0.1",
"web-vitals": "^2.1.4"
Expand Down
20 changes: 14 additions & 6 deletions src/Constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,32 @@ export const DIFFICULTIES = {
},
} as { [index: string]: DifficultyMultipliersType };

export const LOCATIONS = [
{
export const LOCATIONS = {
PIT: {
id: "PIT",
name: "Pittsburgh, PA",
lat: 40.4406,
long: -79.9959,
},
{
SF: {
id: "SF",
name: "San Fransisco, CA",
lat: 37.7749,
long: -122.4194,
},
{
HNL: {
id: "HNL",
name: "Honolulu, HI",
lat: 21.3099,
long: -157.8581,
},
{
SJU: {
id: "SJU",
name: "San Juan, Puero Rico",
lat: 18.4671,
long: -66.1185,
},
] as LocationType[];
} as { [id: string]: LocationType };
export const OUTSKIRTS_WIND_MULTIPLIER = 2; // https://github.com/toddmedema/electrify/issues/96

export const TICK_MS = {
Expand Down
6 changes: 3 additions & 3 deletions src/Types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ export type MonthType =
export type DifficultyType = "Intern" | "Employee" | "Manager" | "VP" | "CEO";
export type SpeedType = "PAUSED" | "SLOW" | "NORMAL" | "FAST";

export type LocationIdType = "PIT" | "SF";
export type LocationIdType = "PIT" | "SF" | "HNL" | "SJU";
export interface LocationType {
id: LocationIdType;
name: string;
lat: number;
long: number;
}

export type FuelNameType = "Coal" | "Wind" | "Sun" | "Natural Gas" | "Uranium";
Expand Down Expand Up @@ -88,8 +90,6 @@ export interface DateType {
monthNumber: number; // 1 - 12
monthsEllapsed: number;
year: number;
sunrise: number;
sunset: number;
}

export interface RawWeatherType {
Expand Down
37 changes: 26 additions & 11 deletions src/components/base/ChartSupplyDemand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
VictoryLine,
VictoryTheme,
} from "victory";
import { getDateFromMinute } from "../../helpers/DateTime";
import { getDateFromMinute, getSunriseSunset } from "../../helpers/DateTime";
import { formatWatts } from "../../helpers/Format";
import { getIntersectionX } from "../../helpers/Math";
import {
Expand All @@ -17,6 +17,7 @@ import {
demandColor,
supplyColor,
} from "../../Theme";
import { LocationType } from "../../Types";

interface ChartData {
minute: number;
Expand All @@ -31,16 +32,17 @@ interface BlackoutEdges {

export interface Props {
currentMinute: number;
location: LocationType;
height?: number;
legend?: boolean;
timeline: ChartData[];
startingYear: number;
}

// TODO how to indicate reality vs forecast? Perhaps current time as a prop, and then split it in the chart
// TODO how to indicate history vs reality vs forecast? Perhaps current time as a prop, and then split it in the chart
// and don't actually differentiate between reality + forecast in data?
const ChartSupplyDemand = (props: Props): JSX.Element => {
const { startingYear, height, legend, timeline } = props;
const { startingYear, height, legend, timeline, location } = props;
// Figure out the boundaries of the chart data
let domainMin = 999999999999;
let domainMax = 0;
Expand All @@ -54,17 +56,30 @@ const ChartSupplyDemand = (props: Props): JSX.Element => {
// Get sunrise and sunset, sliding forward if it's actually in the next day
const date = getDateFromMinute(rangeMin, startingYear);
const midnight = Math.floor(rangeMin / 1440) * 1440;
let sunrise = midnight + date.sunrise;
let sunset = midnight + date.sunset;

let sunrise =
midnight + getSunriseSunset(date, location.lat, location.long).sunrise;
let sunset =
midnight + getSunriseSunset(date, location.lat, location.long).sunset;
if (sunrise < rangeMin) {
sunrise =
midnight +
1440 +
getDateFromMinute(rangeMin + 1440, startingYear).sunrise;
getSunriseSunset(
getDateFromMinute(rangeMin + 1440, startingYear),
location.lat,
location.long
).sunrise;
}
if (sunset < rangeMin) {
sunset =
midnight + 1440 + getDateFromMinute(rangeMin + 1440, startingYear).sunset;
midnight +
1440 +
getSunriseSunset(
getDateFromMinute(rangeMin + 1440, startingYear),
location.lat,
location.long
).sunset;
}

// BLACKOUT CALCULATION
Expand Down Expand Up @@ -95,7 +110,7 @@ const ChartSupplyDemand = (props: Props): JSX.Element => {
prev.minute,
prev.demandW,
d.minute,
d.demandW,
d.demandW
);
blackouts.push({ minute: intersectionTime, value: 0 });
blackouts.push({ minute: intersectionTime, value: domainMax });
Expand All @@ -111,7 +126,7 @@ const ChartSupplyDemand = (props: Props): JSX.Element => {
prev.minute,
prev.demandW,
d.minute,
d.demandW,
d.demandW
);
blackouts.push({ minute: intersectionTime, value: domainMax });
blackouts.push({ minute: intersectionTime, value: 0 });
Expand All @@ -128,10 +143,10 @@ const ChartSupplyDemand = (props: Props): JSX.Element => {
// Divide between historic and forcast
const currentMinute = props.currentMinute || 0;
const historic = [...timeline].filter(
(d: ChartData) => d.minute <= currentMinute,
(d: ChartData) => d.minute <= currentMinute
);
const forecast = [...timeline].filter(
(d: ChartData) => d.minute >= currentMinute,
(d: ChartData) => d.minute >= currentMinute
);

const legendItems = [
Expand Down
9 changes: 5 additions & 4 deletions src/components/views/Facilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function FacilityListItem(props: FacilityListItemProps): JSX.Element {
const percentBuilt = Math.round(
((facility.yearsToBuild - facility.yearsToBuildLeft) /
facility.yearsToBuild) *
100,
100
);
secondaryText = `Building: ${percentBuilt}%, ${Math.ceil(props.facility.yearsToBuildLeft * 12)} months left`;
} else if (facility.peakWh) {
Expand All @@ -81,7 +81,7 @@ function FacilityListItem(props: FacilityListItemProps): JSX.Element {
{...provided.dragHandleProps}
style={getDraggableStyle(
snapshot.isDragging,
provided.draggableProps.style,
provided.draggableProps.style
)}
>
<ListItem disabled={underConstruction} className="facility">
Expand Down Expand Up @@ -218,7 +218,7 @@ export default class Facilities extends React.Component<Props, {}> {

this.props.onReprioritize(
result.source.index,
result.destination.index - result.source.index,
result.destination.index - result.source.index
);
}

Expand All @@ -233,6 +233,7 @@ export default class Facilities extends React.Component<Props, {}> {
height={180}
timeline={game.timeline}
currentMinute={game.date.minute}
location={game.location}
legend={game.speed === "PAUSED"}
startingYear={game.startingYear}
/>
Expand Down Expand Up @@ -273,7 +274,7 @@ export default class Facilities extends React.Component<Props, {}> {
spotInList={i}
listLength={facilitiesCount}
/>
),
)
)}
{provided.placeholder}
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/LoadingContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch<any>): DispatchProps => {
if (!scenario) {
return alert("Unknown scenario ID " + game.scenarioId);
}
const location = LOCATIONS.find((s) => s.id === scenario.locationId);
const location = LOCATIONS[scenario.locationId];
if (!location) {
return alert("Unknown location ID " + scenario.locationId);
}
Expand All @@ -43,7 +43,7 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch<any>): DispatchProps => {
cash: scenario.cash,
customers: 1030000,
location,
}),
})
);

dispatch(loaded());
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/NewGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ interface ScenarioListItemProps {

function ScenarioListItem(props: ScenarioListItemProps): JSX.Element {
const { s, onDetails } = props;
const location = LOCATIONS.find((l) => l.id === s.locationId) || {
const location = LOCATIONS[s.locationId] || {
name: "UNKNOWN",
};
const summary =
Expand Down
4 changes: 1 addition & 3 deletions src/components/views/NewGameDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ export default class NewGameDetails extends React.Component<Props, State> {
SCENARIOS.find((s) => s.id === props.game.scenarioId) || null;
this.state = {
scenario,
location: scenario
? LOCATIONS.find((s) => s.id === scenario.locationId) || null
: null,
location: scenario ? LOCATIONS[scenario.locationId || null] : null,
};
if (props.uid) {
this.loadScores(props.uid);
Expand Down
21 changes: 15 additions & 6 deletions src/data/Weather.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DAYS_PER_MONTH, DAYS_PER_YEAR } from "../Constants";
import { DateType, RawWeatherType } from "../Types";
import { getRandomRange } from "../helpers/Math";
import { getSunriseSunset } from "../helpers/DateTime";

const Papa = require("papaparse");

Expand Down Expand Up @@ -39,7 +40,7 @@ export function initWeather(location: string, callback?: any) {
complete() {
if (weather.length !== EXPECTED_ROWS) {
console.warn(
`Weather data for ${location} appears to be incomplete. Found ${weather.length} rows, expected ${EXPECTED_ROWS}`,
`Weather data for ${location} appears to be incomplete. Found ${weather.length} rows, expected ${EXPECTED_ROWS}`
);
}
if (callback) {
Expand Down Expand Up @@ -78,22 +79,30 @@ export function getWeather(date: DateType): RawWeatherType {
};
}

// 0-1, percent of sun's energy hitting a unit of land relative to max
// 0-1, percent of sun's energy hitting a unit of land relative to max at equator (~1360 w/m2)
// Is later multiplied by cloudiness
// TODO change to watts per sq meter or some fixed value, and verify that it's returning reasonably accurate values per location and season
// (hoping that day length alone is a sufficient proxy / ideally don't need to make it any more complex)
export function getRawSunlightPercent(date: DateType) {
if (date.minuteOfDay >= date.sunrise && date.minuteOfDay <= date.sunset) {
// https://earthobservatory.nasa.gov/features/EnergyBalance/page2.php
// indicates a roughly linear correlation that each degree off from 0*N/S = 0.7% less sunlight
export function getRawSunlightPercent(
date: DateType,
lat: number,
long: number
) {
const { sunrise, sunset } = getSunriseSunset(date, lat, long);
if (date.minuteOfDay >= sunrise && date.minuteOfDay <= sunset) {
const minutesFromDark = Math.min(
date.minuteOfDay - date.sunrise,
date.sunset - date.minuteOfDay,
date.minuteOfDay - sunrise,
sunset - date.minuteOfDay
);
// TODO fix the pointiness, esp in shorter winter months
// Maybe by factoring in day lenght to determine the shape of the curve?

// Day length / minutes from dark used as proxy for season / max sun height
// Rough approximation of solar output: https://www.wolframalpha.com/input/?i=plot+1%2F%281+%2B+e+%5E+%28-0.015+*+%28x+-+260%29%29%29+from+0+to+420
// Solar panels generally follow a Bell curve
// Potential more complex model for solar panels: https://pro.arcgis.com/en/pro-app/3.1/tool-reference/spatial-analyst/how-solar-radiation-is-calculated.htm
return 1 / (1 + Math.pow(Math.E, -0.015 * (minutesFromDark - 260)));
}
return 0;
Expand Down
Loading

0 comments on commit 7cf8cbf

Please sign in to comment.