|
| 1 | +%[text] # Solar Analysis for Any Address |
| 2 | +%[text] Enter a street address and get a complete solar energy analysis for that location. Uses the OpenStreetMap Nominatim geocoder to convert the address to coordinates, then computes sun position and panel output across the year. |
| 3 | + |
| 4 | +%% |
| 5 | +%[text] ## Enter Your Address |
| 6 | +%[text] Change the address below to analyze any location. |
| 7 | + |
| 8 | +address = "3 Apple Hill Drive, Natick, MA"; |
| 9 | + |
| 10 | +%% |
| 11 | +%[text] ## Geocode the Address |
| 12 | + |
| 13 | +[lat, lon, displayName] = geocodeAddress(address); |
| 14 | +fprintf("Location: %s\n", displayName) |
| 15 | +fprintf("Coordinates: %.4f°N, %.4f°E\n", lat, lon) |
| 16 | + |
| 17 | +%% |
| 18 | +%[text] ## Panel Configuration |
| 19 | + |
| 20 | +panelEfficiency = 0.20; |
| 21 | +panelArea = 1.6; % m^2 |
| 22 | +panelTilt = abs(lat); % rule-of-thumb: tilt = latitude |
| 23 | +panelAzimuth = 180; % south-facing (northern hemisphere) |
| 24 | + |
| 25 | +%% |
| 26 | +%[text] ## Show Location on Map |
| 27 | +%[text] Display the location on a geographic map with satellite basemap. |
| 28 | + |
| 29 | +figure('Position', [100 100 700 500]) |
| 30 | +geoplot(lat, lon, 'rp', 'MarkerSize', 20, 'MarkerFaceColor', 'r') |
| 31 | +geobasemap satellite |
| 32 | +title(sprintf("Solar Analysis Site: %s", address)) |
| 33 | +geolimits([lat-0.01 lat+0.01], [lon-0.01 lon+0.01]) |
| 34 | + |
| 35 | +%% |
| 36 | +%[text] ## Sun Path Diagram |
| 37 | +%[text] Compute sun paths for solstices and equinoxes at this location. |
| 38 | + |
| 39 | +figure('Position', [100 100 600 600]) |
| 40 | +dates = [datetime(2024,3,20), datetime(2024,6,21), ... |
| 41 | + datetime(2024,9,22), datetime(2024,12,21)]; |
| 42 | +labels = ["Spring Equinox", "Summer Solstice", "Autumn Equinox", "Winter Solstice"]; |
| 43 | +colors = [0.2 0.7 0.3; 0.85 0.33 0.1; 0.6 0.4 0.0; 0.1 0.4 0.8]; |
| 44 | + |
| 45 | +polaraxes; |
| 46 | +hold on |
| 47 | +for k = 1:4 |
| 48 | + t = datetime(dates(k), 'TimeZone', 'UTC') + minutes(0:5:1439); |
| 49 | + [az, el] = sunPosition(lat, lon, t); |
| 50 | + daytime = el > 0; |
| 51 | + polarplot(deg2rad(az(daytime)), 90 - el(daytime), ... |
| 52 | + 'Color', colors(k,:), 'LineWidth', 2) |
| 53 | +end |
| 54 | +hold off |
| 55 | +ax = gca; |
| 56 | +ax.ThetaZeroLocation = 'top'; |
| 57 | +ax.ThetaDir = 'clockwise'; |
| 58 | +ax.RLim = [0 90]; |
| 59 | +ax.RTickLabel = {'90°','60°','30°','0°'}; |
| 60 | +title(sprintf("Sun Path — %s (%.2f°N)", address, lat)) |
| 61 | +legend(labels, 'Location', 'southoutside', 'Orientation', 'horizontal') |
| 62 | + |
| 63 | +%% |
| 64 | +%[text] ## Monthly Energy Yield |
| 65 | +%[text] Compute the expected monthly clear-sky energy production. |
| 66 | + |
| 67 | +daysPerMonth = [31 29 31 30 31 30 31 31 30 31 30 31]; |
| 68 | +monthlyEnergy = zeros(1, 12); |
| 69 | + |
| 70 | +for m = 1:12 |
| 71 | + t = datetime(2024, m, 15, 'TimeZone', 'UTC') + hours(0:23); |
| 72 | + [az, el] = sunPosition(lat, lon, t); |
| 73 | + w = solarPanelPower(panelEfficiency, panelArea, az, el, panelTilt, panelAzimuth); |
| 74 | + dailyKWh = sum(w) / 1000; |
| 75 | + monthlyEnergy(m) = dailyKWh * daysPerMonth(m); |
| 76 | +end |
| 77 | + |
| 78 | +annualTotal = sum(monthlyEnergy); |
| 79 | + |
| 80 | +%% |
| 81 | +%[text] Plot the monthly breakdown. |
| 82 | + |
| 83 | +figure |
| 84 | +bar(1:12, monthlyEnergy, 'FaceColor', [0.9 0.5 0.1], 'EdgeColor', 'none') |
| 85 | +xlabel("Month") |
| 86 | +ylabel("Energy (kWh)") |
| 87 | +title(sprintf("Monthly Solar Energy — %s", address)) |
| 88 | +subtitle(sprintf("Annual total: %.0f kWh/panel (clear sky) | Tilt: %.0f° South", ... |
| 89 | + annualTotal, panelTilt)) |
| 90 | +xticklabels(["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]) |
| 91 | +grid on |
| 92 | + |
| 93 | +%% |
| 94 | +%[text] ## Daily Power Curves by Season |
| 95 | +%[text] Show how power output varies through the day for each season. |
| 96 | + |
| 97 | +figure |
| 98 | +hold on |
| 99 | +dailyYield = zeros(1, 4); |
| 100 | +for k = 1:4 |
| 101 | + t = datetime(dates(k), 'TimeZone', 'UTC') + minutes(0:10:1439); |
| 102 | + [az, el] = sunPosition(lat, lon, t); |
| 103 | + w = solarPanelPower(panelEfficiency, panelArea, az, el, panelTilt, panelAzimuth); |
| 104 | + plot(hours(t - t(1)), w, 'Color', colors(k,:), 'LineWidth', 1.5) |
| 105 | + dailyYield(k) = trapz(hours(t - t(1)), w) / 1000; |
| 106 | +end |
| 107 | +hold off |
| 108 | +xlabel("Hour of Day (UTC)") |
| 109 | +ylabel("Power (W)") |
| 110 | +title(sprintf("Daily Power Profiles — %s", address)) |
| 111 | +legend(labels + " (" + compose("%.1f", dailyYield) + " kWh)", 'Location', 'northwest') |
| 112 | +grid on |
| 113 | +xlim([0 24]) |
| 114 | + |
| 115 | +%% |
| 116 | +%[text] ## Summary |
| 117 | +%[text] This analysis provides clear-sky estimates. Actual production will be lower due to: |
| 118 | +%[text] - Cloud cover and weather |
| 119 | +%[text] - Shading from buildings and trees |
| 120 | +%[text] - Panel degradation and inverter losses |
| 121 | +%[text] - Temperature effects \ |
| 122 | +%[text] |
| 123 | +%[text] A typical derating factor is 0.75–0.80 for real-world conditions. Multiply the annual total by this factor for a more realistic estimate. |
| 124 | + |
| 125 | +realisticEstimate = annualTotal * 0.77; |
| 126 | +fprintf("Realistic annual estimate (77%% derating): %.0f kWh/panel\n", realisticEstimate) |
| 127 | + |
| 128 | +%% |
| 129 | +%[text] --- |
| 130 | +%[text] *Functions used: `geocodeAddress`, `sunPosition`, `solarPanelPower`, `geoplot`* |
| 131 | + |
| 132 | +%[appendix]{"version":"1.0"} |
| 133 | +%--- |
| 134 | +%[metadata:view] |
| 135 | +% data: {"layout":"inline"} |
| 136 | +%--- |
0 commit comments