// Copyright 2022 Luca Casonato. All rights reserved. MIT license. /** * Pollen API Client for Deno * ========================== * * The Pollen API. * * Docs: https://developers.google.com/maps/documentation/pollen * Source: https://googleapis.deno.dev/v1/pollen:v1.ts */ import { auth, CredentialsClient, GoogleAuth, request } from "/_/base@v1/mod.ts"; export { auth, GoogleAuth }; export type { CredentialsClient }; /** * The Pollen API. */ export class Pollen { #client: CredentialsClient | undefined; #baseUrl: string; constructor(client?: CredentialsClient, baseUrl: string = "https://pollen.googleapis.com/") { this.#client = client; this.#baseUrl = baseUrl; } /** * Returns up to 5 days of daily pollen information in more than 65 * countries, up to 1km resolution. * */ async forecastLookup(opts: ForecastLookupOptions = {}): Promise { const url = new URL(`${this.#baseUrl}v1/forecast:lookup`); if (opts.days !== undefined) { url.searchParams.append("days", String(opts.days)); } if (opts.languageCode !== undefined) { url.searchParams.append("languageCode", String(opts.languageCode)); } if (opts["location.latitude"] !== undefined) { url.searchParams.append("location.latitude", String(opts["location.latitude"])); } if (opts["location.longitude"] !== undefined) { url.searchParams.append("location.longitude", String(opts["location.longitude"])); } if (opts.pageSize !== undefined) { url.searchParams.append("pageSize", String(opts.pageSize)); } if (opts.pageToken !== undefined) { url.searchParams.append("pageToken", String(opts.pageToken)); } if (opts.plantsDescription !== undefined) { url.searchParams.append("plantsDescription", String(opts.plantsDescription)); } const data = await request(url.href, { client: this.#client, method: "GET", }); return data as LookupForecastResponse; } /** * Returns a byte array containing the data of the tile PNG image. * * @param mapType Required. The type of the pollen heatmap. Defines the combination of pollen type and index that the map will graphically represent. * @param x Required. Defines the east-west point in the requested tile. * @param y Required. Defines the north-south point in the requested tile. * @param zoom Required. The map's zoom level. Defines how large or small the contents of a map appear in a map view. * Zoom level 0 is the entire world in a single tile. * Zoom level 1 is the entire world in 4 tiles. * Zoom level 2 is the entire world in 16 tiles. * Zoom level 16 is the entire world in 65,536 tiles. Allowed values: 0-16 */ async mapTypesHeatmapTilesLookupHeatmapTile(mapType: | "MAP_TYPE_UNSPECIFIED" | "TREE_UPI" | "GRASS_UPI" | "WEED_UPI", x: number, y: number, zoom: number): Promise { const url = new URL(`${this.#baseUrl}v1/mapTypes/${ mapType }/heatmapTiles/${ zoom }/${ x }/${ y }`); const data = await request(url.href, { client: this.#client, method: "GET", }); return deserializeHttpBody(data); } } /** * Represents a color in the RGBA color space. This representation is designed * for simplicity of conversion to and from color representations in various * languages over compactness. For example, the fields of this representation * can be trivially provided to the constructor of `java.awt.Color` in Java; it * can also be trivially provided to UIColor's `+colorWithRed:green:blue:alpha` * method in iOS; and, with just a little work, it can be easily formatted into * a CSS `rgba()` string in JavaScript. This reference page doesn't have * information about the absolute color space that should be used to interpret * the RGB value—for example, sRGB, Adobe RGB, DCI-P3, and BT.2020. By default, * applications should assume the sRGB color space. When color equality needs to * be decided, implementations, unless documented otherwise, treat two colors as * equal if all their red, green, blue, and alpha values each differ by at most * `1e-5`. Example (Java): import com.google.type.Color; // ... public static * java.awt.Color fromProto(Color protocolor) { float alpha = * protocolor.hasAlpha() ? protocolor.getAlpha().getValue() : 1.0; return new * java.awt.Color( protocolor.getRed(), protocolor.getGreen(), * protocolor.getBlue(), alpha); } public static Color toProto(java.awt.Color * color) { float red = (float) color.getRed(); float green = (float) * color.getGreen(); float blue = (float) color.getBlue(); float denominator = * 255.0; Color.Builder resultBuilder = Color .newBuilder() .setRed(red / * denominator) .setGreen(green / denominator) .setBlue(blue / denominator); int * alpha = color.getAlpha(); if (alpha != 255) { result.setAlpha( FloatValue * .newBuilder() .setValue(((float) alpha) / denominator) .build()); } return * resultBuilder.build(); } // ... Example (iOS / Obj-C): // ... static UIColor* * fromProto(Color* protocolor) { float red = [protocolor red]; float green = * [protocolor green]; float blue = [protocolor blue]; FloatValue* alpha_wrapper * = [protocolor alpha]; float alpha = 1.0; if (alpha_wrapper != nil) { alpha = * [alpha_wrapper value]; } return [UIColor colorWithRed:red green:green * blue:blue alpha:alpha]; } static Color* toProto(UIColor* color) { CGFloat * red, green, blue, alpha; if (![color getRed:&red green:&green blue:&blue * alpha:&alpha]) { return nil; } Color* result = [[Color alloc] init]; [result * setRed:red]; [result setGreen:green]; [result setBlue:blue]; if (alpha <= * 0.9999) { [result setAlpha:floatWrapperWithValue(alpha)]; } [result * autorelease]; return result; } // ... Example (JavaScript): // ... var * protoToCssColor = function(rgb_color) { var redFrac = rgb_color.red || 0.0; * var greenFrac = rgb_color.green || 0.0; var blueFrac = rgb_color.blue || 0.0; * var red = Math.floor(redFrac * 255); var green = Math.floor(greenFrac * 255); * var blue = Math.floor(blueFrac * 255); if (!('alpha' in rgb_color)) { return * rgbToCssColor(red, green, blue); } var alphaFrac = rgb_color.alpha.value || * 0.0; var rgbParams = [red, green, blue].join(','); return ['rgba(', * rgbParams, ',', alphaFrac, ')'].join(''); }; var rgbToCssColor = * function(red, green, blue) { var rgbNumber = new Number((red << 16) | (green * << 8) | blue); var hexString = rgbNumber.toString(16); var missingZeros = 6 - * hexString.length; var resultBuilder = ['#']; for (var i = 0; i < * missingZeros; i++) { resultBuilder.push('0'); } * resultBuilder.push(hexString); return resultBuilder.join(''); }; // ... */ export interface Color { /** * The fraction of this color that should be applied to the pixel. That is, * the final pixel color is defined by the equation: `pixel color = alpha * * (this color) + (1.0 - alpha) * (background color)` This means that a value * of 1.0 corresponds to a solid color, whereas a value of 0.0 corresponds to * a completely transparent color. This uses a wrapper message rather than a * simple float scalar so that it is possible to distinguish between a default * value and the value being unset. If omitted, this color object is rendered * as a solid color (as if the alpha value had been explicitly given a value * of 1.0). */ alpha?: number; /** * The amount of blue in the color as a value in the interval [0, 1]. */ blue?: number; /** * The amount of green in the color as a value in the interval [0, 1]. */ green?: number; /** * The amount of red in the color as a value in the interval [0, 1]. */ red?: number; } /** * Represents a whole or partial calendar date, such as a birthday. The time of * day and time zone are either specified elsewhere or are insignificant. The * date is relative to the Gregorian Calendar. This can represent one of the * following: * A full date, with non-zero year, month, and day values. * A * month and day, with a zero year (for example, an anniversary). * A year on * its own, with a zero month and a zero day. * A year and month, with a zero * day (for example, a credit card expiration date). Related types: * * google.type.TimeOfDay * google.type.DateTime * google.protobuf.Timestamp */ export interface Date { /** * Day of a month. Must be from 1 to 31 and valid for the year and month, or * 0 to specify a year by itself or a year and month where the day isn't * significant. */ day?: number; /** * Month of a year. Must be from 1 to 12, or 0 to specify a year without a * month and day. */ month?: number; /** * Year of the date. Must be from 1 to 9999, or 0 to specify a date without a * year. */ year?: number; } /** * This object contains the daily forecast information for each day requested. */ export interface DayInfo { /** * The date in UTC at which the pollen forecast data is represented. */ date?: Date; /** * This list will include up to 15 pollen species affecting the location * specified in the request. */ plantInfo?: PlantInfo[]; /** * This list will include up to three pollen types (GRASS, WEED, TREE) * affecting the location specified in the request. */ pollenTypeInfo?: PollenTypeInfo[]; } /** * Additional options for Pollen#forecastLookup. */ export interface ForecastLookupOptions { /** * Required. A number that indicates how many forecast days to request * (minimum value 1, maximum value is 5). */ days?: number; /** * Optional. Allows the client to choose the language for the response. If * data cannot be provided for that language, the API uses the closest match. * Allowed values rely on the IETF BCP-47 standard. The default value is "en". */ languageCode?: string; /** * The latitude in degrees. It must be in the range [-90.0, +90.0]. */ ["location.latitude"]?: number; /** * The longitude in degrees. It must be in the range [-180.0, +180.0]. */ ["location.longitude"]?: number; /** * Optional. The maximum number of daily info records to return per page. The * default and max value is 5, indicating 5 days of data. */ pageSize?: number; /** * Optional. A page token received from a previous daily call. It is used to * retrieve the subsequent page. Note that when providing a value for the page * token, all other request parameters provided must match the previous call * that provided the page token. */ pageToken?: string; /** * Optional. Contains general information about plants, including details on * their seasonality, special shapes and colors, information about allergic * cross-reactions, and plant photos. The default value is "true". */ plantsDescription?: boolean; } /** * Message that represents an arbitrary HTTP body. It should only be used for * payload formats that can't be represented as JSON, such as raw binary or an * HTML page. This message can be used both in streaming and non-streaming API * methods in the request as well as the response. It can be used as a top-level * request field, which is convenient if one wants to extract parameters from * either the URL or HTTP template into the request fields and also want access * to the raw HTTP body. Example: message GetResourceRequest { // A unique * request id. string request_id = 1; // The raw HTTP body is bound to this * field. google.api.HttpBody http_body = 2; } service ResourceService { rpc * GetResource(GetResourceRequest) returns (google.api.HttpBody); rpc * UpdateResource(google.api.HttpBody) returns (google.protobuf.Empty); } * Example with streaming methods: service CaldavService { rpc * GetCalendar(stream google.api.HttpBody) returns (stream google.api.HttpBody); * rpc UpdateCalendar(stream google.api.HttpBody) returns (stream * google.api.HttpBody); } Use of this type only changes how the request and * response bodies are handled, all other features will continue to work * unchanged. */ export interface HttpBody { /** * The HTTP Content-Type header value specifying the content type of the * body. */ contentType?: string; /** * The HTTP request/response body as raw binary. */ data?: Uint8Array; /** * Application specific response metadata. Must be set in the first response * for streaming APIs. */ extensions?: { [key: string]: any }[]; } function serializeHttpBody(data: any): HttpBody { return { ...data, data: data["data"] !== undefined ? encodeBase64(data["data"]) : undefined, }; } function deserializeHttpBody(data: any): HttpBody { return { ...data, data: data["data"] !== undefined ? decodeBase64(data["data"] as string) : undefined, }; } /** * This object contains data representing specific pollen index value, category * and description. */ export interface IndexInfo { /** * Text classification of index numerical score interpretation. The index * consists of six categories: * 0: "None" * 1: "Very low" * 2: "Low" * 3: * "Moderate" * 4: "High" * 5: "Very high */ category?: string; /** * The index's code. This field represents the index for programming purposes * by using snake cases instead of spaces. Example: "UPI". */ code?: | "INDEX_UNSPECIFIED" | "UPI"; /** * The color used to represent the Pollen Index numeric score. */ color?: Color; /** * A human readable representation of the index name. Example: "Universal * Pollen Index". */ displayName?: string; /** * Textual explanation of current index level. */ indexDescription?: string; /** * The index's numeric score. Numeric range is between 0 and 5. */ value?: number; } export interface LookupForecastResponse { /** * Required. This object contains the daily forecast information for each day * requested. */ dailyInfo?: DayInfo[]; /** * Optional. The token to retrieve the next page. */ nextPageToken?: string; /** * The ISO_3166-1 alpha-2 code of the country/region corresponding to the * location provided in the request. This field might be omitted from the * response if the location provided in the request resides in a disputed * territory. */ regionCode?: string; } /** * Contains general information about plants, including details on their * seasonality, special shapes and colors, information about allergic * cross-reactions, and plant photos. */ export interface PlantDescription { /** * Textual description of pollen cross reaction plants. Example: Alder, * Hazel, Hornbeam, Beech, Willow, and Oak pollen. */ crossReaction?: string; /** * A human readable representation of the plant family name. Example: * "Betulaceae (the Birch family)". */ family?: string; /** * Link to the picture of the plant. */ picture?: string; /** * Link to a closeup picture of the plant. */ pictureCloseup?: string; /** * Textual list of explanations of seasons where the pollen is active. * Example: "Late winter, spring". */ season?: string; /** * Textual description of the plants' colors of leaves, bark, flowers or * seeds that helps identify the plant. */ specialColors?: string; /** * Textual description of the plants' shapes of leaves, bark, flowers or * seeds that helps identify the plant. */ specialShapes?: string; /** * The plant's pollen type. For example: "GRASS". A list of all available * codes could be found here. */ type?: | "POLLEN_TYPE_UNSPECIFIED" | "GRASS" | "TREE" | "WEED"; } /** * This object contains the daily information on specific plant. */ export interface PlantInfo { /** * The plant code name. For example: "COTTONWOOD". A list of all available * codes could be found here. */ code?: | "PLANT_UNSPECIFIED" | "ALDER" | "ASH" | "BIRCH" | "COTTONWOOD" | "ELM" | "MAPLE" | "OLIVE" | "JUNIPER" | "OAK" | "PINE" | "CYPRESS_PINE" | "HAZEL" | "GRAMINALES" | "RAGWEED" | "MUGWORT" | "JAPANESE_CEDAR" | "JAPANESE_CYPRESS"; /** * A human readable representation of the plant name. Example: “Cottonwood". */ displayName?: string; /** * This object contains data representing specific pollen index value, * category and description. */ indexInfo?: IndexInfo; /** * Indication of either the plant is in season or not. */ inSeason?: boolean; /** * Contains general information about plants, including details on their * seasonality, special shapes and colors, information about allergic * cross-reactions, and plant photos. */ plantDescription?: PlantDescription; } /** * This object contains the pollen type index and health recommendation * information on specific pollen type. */ export interface PollenTypeInfo { /** * The pollen type's code name. For example: "GRASS" */ code?: | "POLLEN_TYPE_UNSPECIFIED" | "GRASS" | "TREE" | "WEED"; /** * A human readable representation of the pollen type name. Example: "Grass" */ displayName?: string; /** * Textual list of explanations, related to health insights based on the * current pollen levels. */ healthRecommendations?: string[]; /** * Contains the Universal Pollen Index (UPI) data for the pollen type. */ indexInfo?: IndexInfo; /** * Indication whether the plant is in season or not. */ inSeason?: boolean; } function decodeBase64(b64: string): Uint8Array { const binString = atob(b64); const size = binString.length; const bytes = new Uint8Array(size); for (let i = 0; i < size; i++) { bytes[i] = binString.charCodeAt(i); } return bytes; } const base64abc = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","+","/"]; /** * CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727 * Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation * @param data */ function encodeBase64(uint8: Uint8Array): string { let result = "", i; const l = uint8.length; for (i = 2; i < l; i += 3) { result += base64abc[uint8[i - 2] >> 2]; result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)]; result += base64abc[uint8[i] & 0x3f]; } if (i === l + 1) { // 1 octet yet to write result += base64abc[uint8[i - 2] >> 2]; result += base64abc[(uint8[i - 2] & 0x03) << 4]; result += "=="; } if (i === l) { // 2 octets yet to write result += base64abc[uint8[i - 2] >> 2]; result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; result += base64abc[(uint8[i - 1] & 0x0f) << 2]; result += "="; } return result; }