// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.maps.solar.v1; import "google/api/annotations.proto"; import "google/api/client.proto"; import "google/api/field_behavior.proto"; import "google/api/httpbody.proto"; import "google/type/date.proto"; import "google/type/latlng.proto"; import "google/type/money.proto"; option csharp_namespace = "Google.Maps.Solar.V1"; option go_package = "cloud.google.com/go/maps/solar/apiv1/solarpb;solarpb"; option java_multiple_files = true; option java_outer_classname = "SolarServiceProto"; option java_package = "com.google.maps.solar.v1"; option objc_class_prefix = "GGMPV1A"; option php_namespace = "Google\\Maps\\Solar\\V1"; option ruby_package = "Google::Maps::Solar::V1"; // Service definition for the Solar API. service Solar { option (google.api.default_host) = "solar.googleapis.com"; option (google.api.oauth_scopes) = "https://www.googleapis.com/auth/cloud-platform"; // Locates the closest building to a query point. Returns an error with // code `NOT_FOUND` if there are no buildings within approximately 50m of the // query point. rpc FindClosestBuildingInsights(FindClosestBuildingInsightsRequest) returns (BuildingInsights) { option (google.api.http) = { get: "/v1/buildingInsights:findClosest" }; } // Gets solar information for a region surrounding a location. // Returns an error with code `NOT_FOUND` if the location is outside // the coverage area. rpc GetDataLayers(GetDataLayersRequest) returns (DataLayers) { option (google.api.http) = { get: "/v1/dataLayers:get" }; } // Returns an image by its ID. rpc GetGeoTiff(GetGeoTiffRequest) returns (google.api.HttpBody) { option (google.api.http) = { get: "/v1/geoTiff:get" }; } } // Request message for `Solar.FindClosestBuildingInsights`. message FindClosestBuildingInsightsRequest { // Required. The longitude and latitude from which the API looks for the // nearest known building. google.type.LatLng location = 1 [(google.api.field_behavior) = REQUIRED]; // Optional. The minimum quality level allowed in the results. No result with // lower quality than this will be returned. Not specifying this is // equivalent to restricting to HIGH quality only. ImageryQuality required_quality = 3 [(google.api.field_behavior) = OPTIONAL]; // Optional. Whether to require exact quality of the imagery. // If set to false, the `required_quality` field is interpreted as the minimum // required quality, such that HIGH quality imagery may be returned when // `required_quality` is set to MEDIUM. If set to true, `required_quality` // is interpreted as the exact required quality and only `MEDIUM` quality // imagery is returned if `required_quality` is set to `MEDIUM`. bool exact_quality_required = 4 [(google.api.field_behavior) = OPTIONAL]; } // A bounding box in lat/lng coordinates. message LatLngBox { // The southwest corner of the box. google.type.LatLng sw = 1; // The northeast corner of the box. google.type.LatLng ne = 2; } // Response message for `Solar.FindClosestBuildingInsights`. // Information about the location, dimensions, and solar potential of a // building. message BuildingInsights { // The resource name for the building, of the format `building/`. string name = 1; // A point near the center of the building. google.type.LatLng center = 2; // The bounding box of the building. LatLngBox bounding_box = 9; // Date that the underlying imagery was acquired. This is approximate. google.type.Date imagery_date = 3; // When processing was completed on this imagery. google.type.Date imagery_processed_date = 11; // Postal code (e.g., US zip code) this building is contained by. string postal_code = 4; // Administrative area 1 (e.g., in the US, the state) that contains this // building. For example, in the US, the abbreviation might be "MA" or "CA." string administrative_area = 5; // Statistical area (e.g., US census tract) this building is in. string statistical_area = 6; // Region code for the country (or region) this building is in. string region_code = 7; // Solar potential of the building. SolarPotential solar_potential = 8; // The quality of the imagery used to compute the data for this building. ImageryQuality imagery_quality = 10; } // Information about the solar potential of a building. A number of // fields in this are defined in terms of "panels". The fields // [panel_capacity_watts] // [google.maps.solar.v1.SolarPotential.panel_capacity_watts], // [panel_height_meters] // [google.maps.solar.v1.SolarPotential.panel_height_meters], // and [panel_width_meters] // [google.maps.solar.v1.SolarPotential.panel_width_meters] // describe the parameters of the model of panel used in these // calculations. message SolarPotential { // Size of the maximum array - that is, the maximum number of panels that // can fit on the roof. int32 max_array_panels_count = 1; // Capacity, in watts, of the panel used in the calculations. float panel_capacity_watts = 9; // Height, in meters in portrait orientation, of the panel used in // the calculations. float panel_height_meters = 10; // Width, in meters in portrait orientation, of the panel used in // the calculations. float panel_width_meters = 11; // The expected lifetime, in years, of the solar panels. This is // used in the financial calculations. int32 panel_lifetime_years = 12; // Size, in square meters, of the maximum array. float max_array_area_meters2 = 2; // Maximum number of sunshine hours received per year, by any point // on the roof. Sunshine hours are a measure of the total amount of // insolation (energy) received per year. 1 sunshine hour = 1 kWh per kW // (where kW refers to kW of capacity under Standard Testing Conditions). float max_sunshine_hours_per_year = 3; // Equivalent amount of CO2 produced per MWh of grid electricity. This // is a measure of the carbon intensity of grid electricity displaced // by solar electricity. float carbon_offset_factor_kg_per_mwh = 4; // Total size and sunlight quantiles for the part of the roof that // was assigned to some roof segment. Despite the name, this may not // include the entire building. See [building_stats] // [google.maps.solar.v1.SolarPotential.building_stats]. SizeAndSunshineStats whole_roof_stats = 5; // Size and sunlight quantiles for the entire building, including // parts of the roof that were not assigned to some roof segment. // Because the orientations of these parts are not well // characterised, the roof area estimate is unreliable, but the // ground area estimate is reliable. It may be that a more reliable // whole building roof area can be obtained by scaling the roof area // from [whole_roof_stats] // [google.maps.solar.v1.SolarPotential.whole_roof_stats] by // the ratio of the ground areas of `building_stats` and // `whole_roof_stats`. SizeAndSunshineStats building_stats = 13; // Size and sunlight quantiles for each roof segment. repeated RoofSegmentSizeAndSunshineStats roof_segment_stats = 6; // Each [SolarPanel] [google.maps.solar.v1.SolarPanel] // describes a single solar panel. They are listed in the order that // the panel layout algorithm placed this. This is usually, though // not always, in decreasing order of annual energy production. repeated SolarPanel solar_panels = 14; // Each [SolarPanelConfig] // [google.maps.solar.v1.SolarPanelConfig] describes a // different arrangement of solar panels on the roof. They are in // order of increasing number of panels. The `SolarPanelConfig` with // [panels_count] // [google.maps.solar.v1.SolarPanelConfig.panels_count]=N is // based on the first N panels in the `solar_panels` list. This field is only // populated if at least 4 panels can fit on a roof. repeated SolarPanelConfig solar_panel_configs = 7; // A [FinancialAnalysis] // [google.maps.solar.v1.FinancialAnalysis] gives the savings // from going solar assuming a given monthly bill and a given // electricity provider. They are in order of increasing order of // monthly bill amount. This field will be empty for buildings in // areas for which the Solar API does not have enough information to // perform financial computations. repeated FinancialAnalysis financial_analyses = 8; } // Information about the size and sunniness quantiles of a roof segment. message RoofSegmentSizeAndSunshineStats { // Angle of the roof segment relative to the theoretical ground plane. // 0 = parallel to the ground, 90 = perpendicular to the ground. optional float pitch_degrees = 1; // Compass direction the roof segment is pointing in. 0 = North, 90 = // East, 180 = South. For a "flat" roof segment (`pitch_degrees` very // near 0), azimuth is not well defined, so for consistency, we define it // arbitrarily to be 0 (North). optional float azimuth_degrees = 2; // Total size and sunlight quantiles for the roof segment. SizeAndSunshineStats stats = 3; // A point near the center of the roof segment. google.type.LatLng center = 4; // The bounding box of the roof segment. LatLngBox bounding_box = 5; // The height of the roof segment plane, in meters above sea level, // at the point designated by `center`. Together with the pitch, // azimuth, and center location, this fully defines the roof segment // plane. optional float plane_height_at_center_meters = 6; } // Size and sunniness quantiles of a roof, or part of a roof. message SizeAndSunshineStats { // The area of the roof or roof segment, in m^2. This is the roof area // (accounting for tilt), not the ground footprint area. float area_meters2 = 1; // Quantiles of the pointwise sunniness across the area. If there // are N values here, this represents the (N-1)-iles. For example, // if there are 5 values, then they would be the quartiles (min, // 25%, 50%, 75%, max). Values are in annual kWh/kW like // [max_sunshine_hours_per_year] // [google.maps.solar.v1.SolarPotential.max_sunshine_hours_per_year]. repeated float sunshine_quantiles = 2; // The ground footprint area covered by the roof or roof segment, in m^2. float ground_area_meters2 = 3; } // SolarPanel describes the position, orientation, and production of a // single solar panel. See the [panel_height_meters] // [google.maps.solar.v1.SolarPotential.panel_height_meters], // [panel_width_meters] // [google.maps.solar.v1.SolarPotential.panel_width_meters], // and [panel_capacity_watts] // [google.maps.solar.v1.SolarPotential.panel_capacity_watts] // fields in [SolarPotential] // [google.maps.solar.v1.SolarPotential] for information on the // parameters of the panel. message SolarPanel { // The centre of the panel. google.type.LatLng center = 1; // The orientation of the panel. SolarPanelOrientation orientation = 2; // How much sunlight energy this layout captures over the course of a // year, in DC kWh. float yearly_energy_dc_kwh = 3; // Index in [roof_segment_stats] // [google.maps.solar.v1.SolarPotential.roof_segment_stats] // of the `RoofSegmentSizeAndSunshineStats` which corresponds to the // roof segment that this panel is placed on. optional int32 segment_index = 4; } // SolarPanelConfig describes a particular placement of solar panels // on the roof. message SolarPanelConfig { // Total number of panels. Note that this is redundant to (the sum // of) the corresponding fields in [roof_segment_summaries] // [google.maps.solar.v1.SolarPanelConfig.roof_segment_summaries]. int32 panels_count = 1; // How much sunlight energy this layout captures over the course of a // year, in DC kWh, assuming the panels described above. float yearly_energy_dc_kwh = 2; // Information about the production of each roof segment that is carrying // at least one panel in this layout. `roof_segment_summaries[i]` describes // the i-th roof segment, including its size, expected production and // orientation. repeated RoofSegmentSummary roof_segment_summaries = 4; } // Information about a roof segment on the building, with some number of // panels placed on it. message RoofSegmentSummary { // Angle of the roof segment relative to the theoretical ground plane. // 0 = parallel to the ground, 90 = perpendicular to the ground. optional float pitch_degrees = 2; // Compass direction the roof segment is pointing in. 0 = North, 90 = // East, 180 = South. For a "flat" roof segment (`pitch_degrees` very // near 0), azimuth is not well defined, so for consistency, we define it // arbitrarily to be 0 (North). optional float azimuth_degrees = 3; // The total number of panels on this segment. int32 panels_count = 7; // How much sunlight energy this part of the layout captures over the // course of a year, in DC kWh, assuming the panels described above. float yearly_energy_dc_kwh = 8; // Index in [roof_segment_stats] // [google.maps.solar.v1.SolarPotential.roof_segment_stats] // of the corresponding `RoofSegmentSizeAndSunshineStats`. optional int32 segment_index = 9; } // Analysis of the cost and benefits of the optimum solar layout for a // particular electric bill size. message FinancialAnalysis { // The monthly electric bill this analysis assumes. google.type.Money monthly_bill = 3; // Whether this is the bill size selected to be the default bill for the // area this building is in. Exactly one `FinancialAnalysis` in // `BuildingSolarPotential` should have `default_bill` set. bool default_bill = 4; // How much electricity the house uses in an average month, based on the // bill size and the local electricity rates. float average_kwh_per_month = 5; // Index in [solar_panel_configs] // [google.maps.solar.v1.SolarPotential.solar_panel_configs] // of the optimum solar layout for this bill size. This can be -1 // indicating that there is no layout. In this case, the remaining // submessages will be omitted. optional int32 panel_config_index = 6; // Financial information that applies regardless of the financing method // used. FinancialDetails financial_details = 7; // Cost and benefit of leasing the solar panels. LeasingSavings leasing_savings = 8; // Cost and benefit of buying the solar panels with cash. CashPurchaseSavings cash_purchase_savings = 9; // Cost and benefit of buying the solar panels by financing the purchase. FinancedPurchaseSavings financed_purchase_savings = 10; } // Details of a financial analysis. Some of these details are already // stored at higher levels (e.g., out of pocket cost). Total money // amounts are over a lifetime period defined by the // [panel_lifetime_years] // [google.maps.solar.v1.SolarPotential.panel_lifetime_years] // field in [SolarPotential] // [google.maps.solar.v1.SolarPotential]. Note: The out of // pocket cost of purchasing the panels is given in the // [out_of_pocket_cost] // [google.maps.solar.v1.CashPurchaseSavings.out_of_pocket_cost] // field in [CashPurchaseSavings] // [google.maps.solar.v1.CashPurchaseSavings]. message FinancialDetails { // How many AC kWh we think the solar panels will generate in their first // year. float initial_ac_kwh_per_year = 1; // Utility bill for electricity not produced by solar, for the // lifetime of the panels. google.type.Money remaining_lifetime_utility_bill = 2; // Amount of money available from federal incentives; this applies if the // user buys (with or without a loan) the panels. google.type.Money federal_incentive = 3; // Amount of money available from state incentives; this applies if the // user buys (with or without a loan) the panels. google.type.Money state_incentive = 4; // Amount of money available from utility incentives; this applies if the // user buys (with or without a loan) the panels. google.type.Money utility_incentive = 5; // Amount of money the user will receive from Solar Renewable Energy // Credits over the panel lifetime; this applies if the user buys // (with or without a loan) the panels. google.type.Money lifetime_srec_total = 6; // Total cost of electricity the user would have paid over the // lifetime period if they didn't install solar. google.type.Money cost_of_electricity_without_solar = 7; // Whether net metering is allowed. bool net_metering_allowed = 8; // Percentage (0-100) of the user's power supplied by solar. // Valid for the first year but approximately correct for future years. optional float solar_percentage = 9; // The percentage (0-100) of solar electricity production we assumed was // exported to the grid, based on the first quarter of production. This // affects the calculations if net metering is not allowed. optional float percentage_exported_to_grid = 10; } // Financial information that's shared between different financing methods. message SavingsOverTime { // Savings in the first year after panel installation. google.type.Money savings_year1 = 1; // Savings in the first twenty years after panel installation. google.type.Money savings_year20 = 2; // Using the assumed discount rate, what is the present value of the // cumulative 20-year savings? google.type.Money present_value_of_savings_year20 = 3; // Savings in the entire panel lifetime. google.type.Money savings_lifetime = 5; // Using the assumed discount rate, what is the present value of the // cumulative lifetime savings? google.type.Money present_value_of_savings_lifetime = 6; // Indicates whether this scenario is financially viable. Will be false for // scenarios with poor financial viability (e.g., money-losing). bool financially_viable = 4; } // Cost and benefit of leasing a particular configuration of solar panels // with a particular electricity usage. message LeasingSavings { // Whether leases are allowed in this juristiction (leases are not // allowed in some states). If this field is false, then the values in // this message should probably be ignored. bool leases_allowed = 1; // Whether leases are supported in this juristiction by the financial // calculation engine. If this field is false, then the values in this // message should probably be ignored. This is independent of // `leases_allowed`: in some areas leases are allowed, but under conditions // that aren't handled by the financial models. bool leases_supported = 2; // Estimated annual leasing cost. google.type.Money annual_leasing_cost = 3; // How much is saved (or not) over the lifetime period. SavingsOverTime savings = 4; } // Cost and benefit of an outright purchase of a particular configuration // of solar panels with a particular electricity usage. message CashPurchaseSavings { // Initial cost before tax incentives: the amount that must be paid // out-of-pocket. Contrast with `upfront_cost`, which is after tax incentives. google.type.Money out_of_pocket_cost = 1; // Initial cost after tax incentives: it's the amount that must be paid // during first year. Contrast with `out_of_pocket_cost`, which is before tax // incentives. google.type.Money upfront_cost = 2; // The value of all tax rebates. google.type.Money rebate_value = 3; // Number of years until payback occurs. A negative value means payback // never occurs within the lifetime period. optional float payback_years = 4; // How much is saved (or not) over the lifetime period. SavingsOverTime savings = 5; } // Cost and benefit of using a loan to buy a particular configuration // of solar panels with a particular electricity usage. message FinancedPurchaseSavings { // Annual loan payments. google.type.Money annual_loan_payment = 1; // The value of all tax rebates (including Federal Investment Tax Credit // (ITC)). google.type.Money rebate_value = 2; // The interest rate on loans assumed in this set of calculations. float loan_interest_rate = 3; // How much is saved (or not) over the lifetime period. SavingsOverTime savings = 4; } // Request message for `Solar.GetDataLayers`. message GetDataLayersRequest { // Required. The longitude and latitude for the center of the region to get // data for. google.type.LatLng location = 1 [(google.api.field_behavior) = REQUIRED]; // Required. The radius, in meters, defining the region surrounding that // centre point for which data should be returned. The limitations // on this value are: // // * Any value up to 100m can always be specified. // * Values over 100m can be specified, as long as // `radius_meters` <= `pixel_size_meters * 1000`. // * However, for values over 175m, the `DataLayerView` in the // request must not include monthly flux or hourly shade. float radius_meters = 2 [(google.api.field_behavior) = REQUIRED]; // Optional. The desired subset of the data to return. DataLayerView view = 3 [(google.api.field_behavior) = OPTIONAL]; // Optional. The minimum quality level allowed in the results. No result with // lower quality than this will be returned. Not specifying this is // equivalent to restricting to HIGH quality only. ImageryQuality required_quality = 5 [(google.api.field_behavior) = OPTIONAL]; // Optional. The minimum scale, in meters per pixel, of the data to return. // Values of 0.1 (the default, if this field is not set explicitly), // 0.25, 0.5, and 1.0 are supported. Imagery components whose normal // resolution is less than `pixel_size_meters` will be returned at // the resolution specified by `pixel_size_meters`; imagery // components whose normal resolution is equal to or greater than // `pixel_size_meters` will be returned at that normal resolution. float pixel_size_meters = 6 [(google.api.field_behavior) = OPTIONAL]; // Optional. Whether to require exact quality of the imagery. // If set to false, the `required_quality` field is interpreted as the minimum // required quality, such that HIGH quality imagery may be returned when // `required_quality` is set to MEDIUM. If set to true, `required_quality` // is interpreted as the exact required quality and only `MEDIUM` quality // imagery is returned if `required_quality` is set to `MEDIUM`. bool exact_quality_required = 7 [(google.api.field_behavior) = OPTIONAL]; } // Information about the solar potential of a region. The actual data // are contained in a number of GeoTIFF files covering the requested // region, for which this message contains URLs: Each string in the // `DataLayers` message contains a URL from which the // corresponding GeoTIFF can be fetched. These URLs are valid for a // few hours after they've been generated. Most of the GeoTIFF files // are at a resolution of 0.1m/pixel, but the monthly flux file is at // 0.5m/pixel, and the hourly shade files are at 1m/pixel. If a // `pixel_size_meters` value was specified in the // `GetDataLayersRequest`, then the minimum resolution in the GeoTIFF // files will be that value. message DataLayers { // When the source imagery (from which all the other data are derived) in this // region was taken. It is necessarily somewhat approximate, as the images may // have been taken over more than one day. google.type.Date imagery_date = 1; // When processing was completed on this imagery. google.type.Date imagery_processed_date = 2; // The URL for an image of the DSM (Digital Surface Model) of the region. // Values are in meters above EGM96 geoid (i.e., sea level). Invalid locations // (where we don't have data) are stored as -9999. string dsm_url = 3; // The URL for an image of RGB data (aerial photo) of the region. string rgb_url = 4; // The URL for the building mask image: one bit per pixel saying whether that // pixel is considered to be part of a rooftop or not. string mask_url = 5; // The URL for the annual flux map (annual sunlight on roofs) of the region. // Values are kWh/kW/year. This is *unmasked flux*: flux is computed for every // location, not just building rooftops. Invalid locations are stored as // -9999: locations outside our coverage area will be invalid, and a few // locations inside the coverage area, where we were unable to calculate flux, // will also be invalid. string annual_flux_url = 6; // The URL for the monthly flux map (sunlight on roofs, broken down by month) // of the region. Values are kWh/kW/year. The GeoTIFF pointed to by this URL // will contain twelve bands, corresponding to January...December, in order. string monthly_flux_url = 7; // Twelve URLs for hourly shade, corresponding to January...December, in // order. Each GeoTIFF will contain 24 bands, corresponding to the 24 hours of // the day. Each pixel is a 32 bit integer, corresponding to the (up to) 31 // days of that month; a 1 bit means that the corresponding location is able // to see the sun at that day, of that hour, of that month. Invalid locations // are stored as -9999 (since this is negative, it has bit 31 set, and no // valid value could have bit 31 set as that would correspond to the 32nd day // of the month). // // An example may be useful. If you want to know whether a point (at // pixel location (x, y)) saw sun at 4pm on the 22nd of June you // would: // // 1. fetch the sixth URL in this list (corresponding to June). // 1. look up the 17th channel (corresponding to 4pm). // 1. read the 32-bit value at (x, y). // 1. read bit 21 of the value (corresponding to the 22nd of the month). // 1. if that bit is a 1, then that spot saw the sun at 4pm 22 June. // // More formally: // Given `month` (1-12), `day` (1...month max; February has 28 days) // and `hour` (0-23), the shade/sun for that month/day/hour at a // position `(x, y)` is the bit // ``` // (hourly_shade[month - 1])(x, y)[hour] & (1 << (day - 1)) // ``` // where `(x, y)` is spatial indexing, `[month - 1]` refers to // fetching the `month - 1`st URL (indexing from zero), `[hour]` is // indexing into the channels, and a final non-zero result means // "sunny". There are no leap days, and DST doesn't exist (all days // are 24 hours long; noon is always "standard time" noon). repeated string hourly_shade_urls = 8; // The quality of the result's imagery. ImageryQuality imagery_quality = 9; } // Request message for `Solar.GetGeoTiff`. message GetGeoTiffRequest { // Required. The ID of the asset being requested. string id = 1 [(google.api.field_behavior) = REQUIRED]; } // What subset of the solar information to return. enum DataLayerView { // Equivalent to FULL. DATA_LAYER_VIEW_UNSPECIFIED = 0; // Get the DSM only. DSM_LAYER = 1; // Get the DSM, RGB, and mask. IMAGERY_LAYERS = 2; // Get the DSM, RGB, mask, and annual flux. IMAGERY_AND_ANNUAL_FLUX_LAYERS = 3; // Get the DSM, RGB, mask, annual flux, and monthly flux. IMAGERY_AND_ALL_FLUX_LAYERS = 4; // Get all data. FULL_LAYERS = 5; } // The quality of the imagery used to compute some API result. // // Note: Regardless of imagery quality level, DSM outputs always have a // resolution of 0.1 m/pixel, monthly flux outputs always have a resolution of // 0.5 m/pixel, and hourly shade outputs always have a resolution of 1 m/pixel. enum ImageryQuality { // No quality is known. IMAGERY_QUALITY_UNSPECIFIED = 0; // The underlying imagery and DSM data were processed at 0.1 m/pixel. HIGH = 1; // The underlying imagery and DSM data were processed at 0.25 m/pixel. MEDIUM = 2; // The underlying imagery and DSM data were processed at 0.5 m/pixel. LOW = 3; } // The orientation of a solar panel. This must be interpreted relative to the // azimuth of the roof segment that the panel is placed on. enum SolarPanelOrientation { // No panel orientation is known. SOLAR_PANEL_ORIENTATION_UNSPECIFIED = 0; // A `LANDSCAPE` panel has its long edge perpendicular to the // azimuth direction of the roof segment that it is placed on. LANDSCAPE = 1; // A `PORTRAIT` panel has its long edge parallel to the azimuth // direction of the roof segment that it is placed on. PORTRAIT = 2; }