Visualizing potential solar panel placements in Google Maps

3 months ago

Visualizing potential solar panel placements in Google Maps

This is how you can visualize potential solar panel placements with the building insights in the new Solar API, on dynamic maps!

An address with coverage: 585 Franklin Street Google Maps Solar API Panel Demo

The building insights coverage are currently limited in specific regions, see the approximate coverage here.

This approach does not currently implement the depth coercion on the polygon caused by the pitch angle; this can however be done by applying a formula on the polygon.

This is a continuation of my article talking about implementing the Solar API data layers on dynamic maps.

With the new Solar API, we also get access to very rich energy saving and panels planning data. I'm not educated enough in the area of solar potential to make sense of most of the values, however, I am occasionally good at interpreting data to create an interface of visualizing it!

That said, I have replicated the 6th page of the Solar API demo which demonstrates the use of the potential panel config data. This is how I did it, and how you can add a quick widget to your own website!

I used my own google-maps-solar-api package for a fully typed Solar API interface, and since the last article, I have fulfilled the package to fully cover the Solar API references, and I added the possibility to pass a proxy URL instead of an API key, for use in frontend clients. Rate limiting is important!

I'm going to assume from this point that you have a data layer overlay on your dynamic map, with at the very least an imagery layer and have called the building insights endpoint:

const buildingInsights = await findClosestBuildingInsights(apiKeyOrProxyUrl, { location: coordinate });

Each SolarPanelConfig in the solarPotential object refers to one set up of panel placements (and other data). This is a sorted array that goes from the least amount of panels to the most. If we have a slider that defines the current config index from 0 to the length of the solarPanelConfigs array, we can assume that we're working with a single solar panel config.

How I did my implementation is that I iterate through each RoofSegmentSummary and adding the panels count in each iteration, as to use later with the solar panels config:

let panelsCount = 0; solarPanelConfig.roofSegmentSummaries.forEach((roofSegmentSummary) => { panelsCount += roofSegmentSummary.panelsCount; });

Before adding to the panelsCount, we want to filter out all the solar panels that are in our current segment index in our segment summary:

buildingInsights.solarPotential.solarPanels .filter((solarPanel) => solarPanel.segmentIndex === roofSegmentSummary.segmentIndex);

Now we have an array of solar panels, we want to slice the array until we reach the current config panels count, or the end of the filtered solar panels array:

buildingInsights.solarPotential.solarPanels .filter((solarPanel) => solarPanel.segmentIndex === roofSegmentSummary.segmentIndex) .slice(0, Math.min(solarPanelConfig.panelsCount - panelsCount, roofSegmentSummary.panelsCount)) .forEach((solarPanel) => { });

We can now work with our individual solar panels and start with getting the dimensions right! The panel width and height meters we receive in the solar potential object are in portrait orientation. If our panel is in landscape orientation, we want to switch them around:

let height = this.buildingInsights.solarPotential.panelHeightMeters / 2; let width = this.buildingInsights.solarPotential.panelWidthMeters / 2; if(solarPanel.orientation === "LANDSCAPE") { const previousHeight = height; height = width; width = previousHeight; }

For some performance considerations, I defined a map to match previously processed solar panels with existing polygons, so I'll do the same here, first we check if our map has the current solar panel, otherwise we process it. To process the solar panel, we need to convert the dimensions to a rectangle in latitude and longitude coordinates.

Using the spherical geometry library, we can start with getting the upmost top, upmost left, and upmost right coordinate, individually, by computing an offset of the panel dimensions in each direction - relative to the solar panel azimuth degrees:

const top = google.maps.geometry.spherical.computeOffset(center, height, angle + 0); const right = google.maps.geometry.spherical.computeOffset(center, width, angle + 90); const left = google.maps.geometry.spherical.computeOffset(center, width, angle + 270);

With these presets, we can compute to each corner of the solar panel with the help of these 3 edges:

const topRight = google.maps.geometry.spherical.computeOffset(top, width, angle + 90); const bottomRight = google.maps.geometry.spherical.computeOffset(right, height, angle + 180); const bottomLeft = google.maps.geometry.spherical.computeOffset(left, height, angle + 180); const topLeft = google.maps.geometry.spherical.computeOffset(left, height, angle + 0);

With these 4 corners, we can construct a polygon and retire it to our map:

solarPanelPolygonReferences.set(solarPanel, new google.maps.Polygon({ fillColor: "#2B2478", fillOpacity: 0.8, strokeWeight: 1, strokeColor: "#AAAFCA", strokeOpacity: 1, geodesic: false, paths: [ topRight, bottomRight, bottomLeft, topLeft ] }));

Now we can continue and read the polygon from our map, and attach it to our dynamic map:

const polygon = solarPanelPolygonReferences.get(solarPanel)!; polygon.setMap(map);

Save it to another array, or use the same array, to then iterate it and remove it from the map, when changing the config segment!

That's it! You can find the entire implementation in my google-maps-solar-api-panels-demo repository, or test the widget out in your website right away with a script tag:

const element = document.getElementById("solar-panels-container"); new window.SolarPanelsMap("API_KEY_OR_PROXY_URL", element);

If your website is publicly accessible, consider using your own proxy URL for the Solar API instead of putting your Solar API key out in public.