diff --git a/.gitignore b/.gitignore index 9c44a00..7b70bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ templates/ts_index.html templates/monthly_index.html static/plots/* static/completed/* +templates/on_map3.html +awscodebuild/.aws/ +awscodebuild/imageDetail.json diff --git a/Dockerfile b/Dockerfile index a51f0b3..5ead1fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# Use a minimal image -FROM python:3.11.7 +# Update base image for KEV CVE-2024-36971 +FROM python:3.11.10 LABEL MAINTAINER="Dmitry Duplyakin " @@ -58,5 +58,4 @@ EXPOSE 80 #CMD ["python", "proto.py", "--production"] # Version that allows following a file with out and err messages -CMD ["/bin/bash", "-c", "python proto.py --production >> proto.out 2>&1"] - +CMD ["/bin/bash", "-c", "python proto.py --production 2>&1"] diff --git a/Makefile b/Makefile deleted file mode 100755 index 79d7668..0000000 --- a/Makefile +++ /dev/null @@ -1,50 +0,0 @@ --include env_make - -# BASE_IMAGE_TAG ?= 3-alpine -BASE_IMAGE_TAG ?= 3 - -PROJECT_NAME=tap-api - -ifdef AWS_ACCOUNT_ID - REGISTRY-IDS=$(AWS_ACCOUNT_ID) -else - $(error AWS_ACCOUNT_ID is not set) -endif - -REPO = $(REGISTRY-IDS).dkr.ecr.us-west-2.amazonaws.com/nrel-$(PROJECT_NAME) - -ifdef RELEASE_SHA2 - HEAD_VER=$(RELEASE_SHA2) -else ifdef RELEASE_SHA1 - HEAD_VER=$(RELEASE_SHA1) -else - HEAD_VER=$(shell git log -1 --pretty=tformat:%h) -endif - -$(info HEAD_VER="$(HEAD_VER)") - -ifdef BRANCH_NAME2 - BRANCH_NAME=$(BRANCH_NAME2) -else ifdef BRANCH_NAME1 - BRANCH_NAME=$(BRANCH_NAME1) -else - BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) -endif - -$(info BRANCH_NAME="$(BRANCH_NAME)") - -# git release version - use for rollbacks - -TAG ?= $(BASE_IMAGE_TAG)-$(BRANCH_NAME)-$(HEAD_VER) - -.PHONY: build push - -build: - docker build -t $(REPO):$(TAG) \ - --build-arg BASE_IMAGE_TAG=$(BASE_IMAGE_TAG) \ - --build-arg AWS_ACCOUNT_ID=$(REGISTRY-IDS) \ - -f Dockerfile \ - ./ - -push: - docker push $(REPO):$(TAG) diff --git a/README.md b/README.md index d68c056..d6bff82 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,11 @@ docker build -t tap-api:latest . Run: ```shell +docker run -p 8080:80 -it tap-api:latest python proto.py --production +``` + +For troubleshooting inside the container, run (and you will have the prompt change and try the following commands inside the container): +```shell docker run -p 8080:80 -it tap-api:latest /bin/bash ``` diff --git a/apidoc-template/css/style.css b/apidoc-template-DEPRECATED-073124/css/style.css similarity index 100% rename from apidoc-template/css/style.css rename to apidoc-template-DEPRECATED-073124/css/style.css diff --git a/apidoc-template/fonts/glyphicons-halflings-regular.eot b/apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from apidoc-template/fonts/glyphicons-halflings-regular.eot rename to apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.eot diff --git a/apidoc-template/fonts/glyphicons-halflings-regular.svg b/apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from apidoc-template/fonts/glyphicons-halflings-regular.svg rename to apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.svg diff --git a/apidoc-template/fonts/glyphicons-halflings-regular.ttf b/apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from apidoc-template/fonts/glyphicons-halflings-regular.ttf rename to apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.ttf diff --git a/apidoc-template/fonts/glyphicons-halflings-regular.woff b/apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from apidoc-template/fonts/glyphicons-halflings-regular.woff rename to apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.woff diff --git a/apidoc-template/fonts/glyphicons-halflings-regular.woff2 b/apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from apidoc-template/fonts/glyphicons-halflings-regular.woff2 rename to apidoc-template-DEPRECATED-073124/fonts/glyphicons-halflings-regular.woff2 diff --git a/apidoc-template/img/favicon.ico b/apidoc-template-DEPRECATED-073124/img/favicon.ico similarity index 100% rename from apidoc-template/img/favicon.ico rename to apidoc-template-DEPRECATED-073124/img/favicon.ico diff --git a/apidoc-template/index.html b/apidoc-template-DEPRECATED-073124/index.html similarity index 100% rename from apidoc-template/index.html rename to apidoc-template-DEPRECATED-073124/index.html diff --git a/apidoc-template/locales/ca.js b/apidoc-template-DEPRECATED-073124/locales/ca.js similarity index 100% rename from apidoc-template/locales/ca.js rename to apidoc-template-DEPRECATED-073124/locales/ca.js diff --git a/apidoc-template/locales/cs.js b/apidoc-template-DEPRECATED-073124/locales/cs.js similarity index 100% rename from apidoc-template/locales/cs.js rename to apidoc-template-DEPRECATED-073124/locales/cs.js diff --git a/apidoc-template/locales/de.js b/apidoc-template-DEPRECATED-073124/locales/de.js similarity index 100% rename from apidoc-template/locales/de.js rename to apidoc-template-DEPRECATED-073124/locales/de.js diff --git a/apidoc-template/locales/es.js b/apidoc-template-DEPRECATED-073124/locales/es.js similarity index 100% rename from apidoc-template/locales/es.js rename to apidoc-template-DEPRECATED-073124/locales/es.js diff --git a/apidoc-template/locales/fr.js b/apidoc-template-DEPRECATED-073124/locales/fr.js similarity index 100% rename from apidoc-template/locales/fr.js rename to apidoc-template-DEPRECATED-073124/locales/fr.js diff --git a/apidoc-template/locales/it.js b/apidoc-template-DEPRECATED-073124/locales/it.js similarity index 100% rename from apidoc-template/locales/it.js rename to apidoc-template-DEPRECATED-073124/locales/it.js diff --git a/apidoc-template/locales/locale.js b/apidoc-template-DEPRECATED-073124/locales/locale.js similarity index 100% rename from apidoc-template/locales/locale.js rename to apidoc-template-DEPRECATED-073124/locales/locale.js diff --git a/apidoc-template/locales/nl.js b/apidoc-template-DEPRECATED-073124/locales/nl.js similarity index 100% rename from apidoc-template/locales/nl.js rename to apidoc-template-DEPRECATED-073124/locales/nl.js diff --git a/apidoc-template/locales/pl.js b/apidoc-template-DEPRECATED-073124/locales/pl.js similarity index 100% rename from apidoc-template/locales/pl.js rename to apidoc-template-DEPRECATED-073124/locales/pl.js diff --git a/apidoc-template/locales/pt_br.js b/apidoc-template-DEPRECATED-073124/locales/pt_br.js similarity index 100% rename from apidoc-template/locales/pt_br.js rename to apidoc-template-DEPRECATED-073124/locales/pt_br.js diff --git a/apidoc-template/locales/ro.js b/apidoc-template-DEPRECATED-073124/locales/ro.js similarity index 100% rename from apidoc-template/locales/ro.js rename to apidoc-template-DEPRECATED-073124/locales/ro.js diff --git a/apidoc-template/locales/ru.js b/apidoc-template-DEPRECATED-073124/locales/ru.js similarity index 100% rename from apidoc-template/locales/ru.js rename to apidoc-template-DEPRECATED-073124/locales/ru.js diff --git a/apidoc-template/locales/tr.js b/apidoc-template-DEPRECATED-073124/locales/tr.js similarity index 100% rename from apidoc-template/locales/tr.js rename to apidoc-template-DEPRECATED-073124/locales/tr.js diff --git a/apidoc-template/locales/vi.js b/apidoc-template-DEPRECATED-073124/locales/vi.js similarity index 100% rename from apidoc-template/locales/vi.js rename to apidoc-template-DEPRECATED-073124/locales/vi.js diff --git a/apidoc-template/locales/zh.js b/apidoc-template-DEPRECATED-073124/locales/zh.js similarity index 100% rename from apidoc-template/locales/zh.js rename to apidoc-template-DEPRECATED-073124/locales/zh.js diff --git a/apidoc-template/locales/zh_cn.js b/apidoc-template-DEPRECATED-073124/locales/zh_cn.js similarity index 100% rename from apidoc-template/locales/zh_cn.js rename to apidoc-template-DEPRECATED-073124/locales/zh_cn.js diff --git a/apidoc-template/main.js b/apidoc-template-DEPRECATED-073124/main.js similarity index 100% rename from apidoc-template/main.js rename to apidoc-template-DEPRECATED-073124/main.js diff --git a/apidoc-template/utils/handlebars_helper.js b/apidoc-template-DEPRECATED-073124/utils/handlebars_helper.js similarity index 100% rename from apidoc-template/utils/handlebars_helper.js rename to apidoc-template-DEPRECATED-073124/utils/handlebars_helper.js diff --git a/apidoc-template/utils/send_sample_request.js b/apidoc-template-DEPRECATED-073124/utils/send_sample_request.js similarity index 100% rename from apidoc-template/utils/send_sample_request.js rename to apidoc-template-DEPRECATED-073124/utils/send_sample_request.js diff --git a/apidoc-template/utils/send_sample_request_utils.js b/apidoc-template-DEPRECATED-073124/utils/send_sample_request_utils.js similarity index 100% rename from apidoc-template/utils/send_sample_request_utils.js rename to apidoc-template-DEPRECATED-073124/utils/send_sample_request_utils.js diff --git a/apidoc-template/vendor/bootstrap.min.css b/apidoc-template-DEPRECATED-073124/vendor/bootstrap.min.css similarity index 100% rename from apidoc-template/vendor/bootstrap.min.css rename to apidoc-template-DEPRECATED-073124/vendor/bootstrap.min.css diff --git a/apidoc-template/vendor/bootstrap.min.js b/apidoc-template-DEPRECATED-073124/vendor/bootstrap.min.js similarity index 100% rename from apidoc-template/vendor/bootstrap.min.js rename to apidoc-template-DEPRECATED-073124/vendor/bootstrap.min.js diff --git a/apidoc-template/vendor/diff_match_patch.min.js b/apidoc-template-DEPRECATED-073124/vendor/diff_match_patch.min.js similarity index 100% rename from apidoc-template/vendor/diff_match_patch.min.js rename to apidoc-template-DEPRECATED-073124/vendor/diff_match_patch.min.js diff --git a/apidoc-template/vendor/handlebars.min.js b/apidoc-template-DEPRECATED-073124/vendor/handlebars.min.js similarity index 100% rename from apidoc-template/vendor/handlebars.min.js rename to apidoc-template-DEPRECATED-073124/vendor/handlebars.min.js diff --git a/apidoc-template/vendor/jquery.min.js b/apidoc-template-DEPRECATED-073124/vendor/jquery.min.js similarity index 100% rename from apidoc-template/vendor/jquery.min.js rename to apidoc-template-DEPRECATED-073124/vendor/jquery.min.js diff --git a/apidoc-template/vendor/list.min.js b/apidoc-template-DEPRECATED-073124/vendor/list.min.js similarity index 100% rename from apidoc-template/vendor/list.min.js rename to apidoc-template-DEPRECATED-073124/vendor/list.min.js diff --git a/apidoc-template/vendor/lodash.custom.min.js b/apidoc-template-DEPRECATED-073124/vendor/lodash.custom.min.js similarity index 100% rename from apidoc-template/vendor/lodash.custom.min.js rename to apidoc-template-DEPRECATED-073124/vendor/lodash.custom.min.js diff --git a/apidoc-template/vendor/path-to-regexp/LICENSE b/apidoc-template-DEPRECATED-073124/vendor/path-to-regexp/LICENSE similarity index 100% rename from apidoc-template/vendor/path-to-regexp/LICENSE rename to apidoc-template-DEPRECATED-073124/vendor/path-to-regexp/LICENSE diff --git a/apidoc-template/vendor/path-to-regexp/index.js b/apidoc-template-DEPRECATED-073124/vendor/path-to-regexp/index.js similarity index 100% rename from apidoc-template/vendor/path-to-regexp/index.js rename to apidoc-template-DEPRECATED-073124/vendor/path-to-regexp/index.js diff --git a/apidoc-template/vendor/polyfill.js b/apidoc-template-DEPRECATED-073124/vendor/polyfill.js similarity index 100% rename from apidoc-template/vendor/polyfill.js rename to apidoc-template-DEPRECATED-073124/vendor/polyfill.js diff --git a/apidoc-template/vendor/prism.css b/apidoc-template-DEPRECATED-073124/vendor/prism.css similarity index 100% rename from apidoc-template/vendor/prism.css rename to apidoc-template-DEPRECATED-073124/vendor/prism.css diff --git a/apidoc-template/vendor/prism.js b/apidoc-template-DEPRECATED-073124/vendor/prism.js similarity index 100% rename from apidoc-template/vendor/prism.js rename to apidoc-template-DEPRECATED-073124/vendor/prism.js diff --git a/apidoc-template/vendor/require.min.js b/apidoc-template-DEPRECATED-073124/vendor/require.min.js similarity index 100% rename from apidoc-template/vendor/require.min.js rename to apidoc-template-DEPRECATED-073124/vendor/require.min.js diff --git a/apidoc-template/vendor/semver.min.js b/apidoc-template-DEPRECATED-073124/vendor/semver.min.js similarity index 100% rename from apidoc-template/vendor/semver.min.js rename to apidoc-template-DEPRECATED-073124/vendor/semver.min.js diff --git a/apidoc-template/vendor/webfontloader.js b/apidoc-template-DEPRECATED-073124/vendor/webfontloader.js similarity index 100% rename from apidoc-template/vendor/webfontloader.js rename to apidoc-template-DEPRECATED-073124/vendor/webfontloader.js diff --git a/apidoc.json b/apidoc.json index 5bdabea..c622826 100644 --- a/apidoc.json +++ b/apidoc.json @@ -1,7 +1,7 @@ { - "name": "DW-TAP API", + "name": "WindWatts API", "version": "1.0.0", - "description": "API for NREL's DW-TAP Project", - "title": "DW-TAP API", - "url" : "https://dw-tap.hpc.nrel.gov/v1" + "description": "API for NREL's WindWatts Tool", + "title": "WindWatts API", + "url" : "https://dw-tap.nrel.gov/api" } diff --git a/awscodebuild/.env b/awscodebuild/.env new file mode 100644 index 0000000..d8b3c97 --- /dev/null +++ b/awscodebuild/.env @@ -0,0 +1,9 @@ +## PROJECT SETTINGS +# PROJECT_HANDLE=tap +# APP_NAME=api +# MAKEFILE_PATH=./awscodebuild/Makefile +# APPFLEET_RELEASE_NAME=dev +# APPFLEET_DEPLOY_VERSION=0.0.12-1.0.0-alpine-3c676c3 +# BASE_IMAGE_TAG=3-alpine +# DIFF_ONLY=False +# SLEEP_ONLY=True diff --git a/awscodebuild/Makefile b/awscodebuild/Makefile new file mode 100644 index 0000000..70e7605 --- /dev/null +++ b/awscodebuild/Makefile @@ -0,0 +1,146 @@ +-include env_make + +# Required environment variables check +REQUIRED_VARS := PROJECT_HANDLE APP_NAME MAKEFILE_PATH APPFLEET_RELEASE_NAME APPFLEET_DEPLOY_VERSION BASE_IMAGE_TAG +$(foreach var,$(REQUIRED_VARS),$(if $(value $(var)),,$(error Environment variable $(var) is not set))) + +# Environment variables (can be overridden by command-line arguments) +PROJECT_NAME = $(PROJECT_HANDLE)-$(APP_NAME) +WORKDIR ?= $(or $(dir $(MAKEFILE_PATH)), $(CURDIR)) + +# Display the WORKDIR value +$(info Using WORKDIR: $(WORKDIR)) + +# Validate that WORKDIR is an existing directory +ifeq ("$(wildcard $(WORKDIR))","") + $(error Error: WORKDIR '$(WORKDIR)' does not exist or is not accessible.) +endif + +REGISTRY-IDS = 991404956194 +REPO = $(REGISTRY-IDS).dkr.ecr.us-west-2.amazonaws.com/nrel-$(PROJECT_NAME) +NAME = $(PROJECT_NAME) + +ifdef RELEASE_SHA2 + HEAD_VER=$(RELEASE_SHA2) +else ifdef RELEASE_SHA1 + HEAD_VER=$(RELEASE_SHA1) +else + HEAD_VER=$(shell git log -1 --pretty=tformat:%h) +endif + +$(info HEAD_VER="$(HEAD_VER)") + +ifdef BRANCH_NAME2 + BRANCH_NAME=$(BRANCH_NAME2) +else ifdef BRANCH_NAME1 + BRANCH_NAME=$(BRANCH_NAME1) +else + BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) +endif + +# Normalize branch name for Docker tag compatibility +BRANCH_NAME_SAFE = $(subst /,-,$(BRANCH_NAME)) + +$(info BRANCH_NAME_SAFE="$(BRANCH_NAME_SAFE)") + +# Use CodeBuild build number if present, otherwise fallback to timestamp for uniqueness +ifdef CODEBUILD_BUILD_NUMBER + UNIQUE_ID=$(CODEBUILD_BUILD_NUMBER) + $(info Using CODEBUILD_BUILD_NUMBER: $(CODEBUILD_BUILD_NUMBER)) +else + UNIQUE_ID=$(shell date +%Y%m%d%H%M%S) + $(info Using UNIQUE_ID generated from timestamp: $(UNIQUE_ID)) +endif + +$(info UNIQUE_ID="$(UNIQUE_ID)") + +# git release version - use for rollbacks +TAG ?= $(BASE_IMAGE_TAG)-$(APPFLEET_RELEASE_NAME)-$(BRANCH_NAME_SAFE)-$(HEAD_VER)-$(UNIQUE_ID) + +$(info TAG="$(TAG)") + +default: release + +# Optional build targets +TARGET_ARG := $(if $(BUILD_TARGET),--target $(BUILD_TARGET),) + +# Updated build target with optional BUILD_TARGET +build: + $(info Running Docker build command:) + $(info docker build -t $(REPO):$(TAG) --platform linux/amd64 --build-arg BASE_IMAGE_TAG=$(BASE_IMAGE_TAG) $(TARGET_ARG) $(WORKDIR)) + docker build -t $(REPO):$(TAG) \ + --platform linux/amd64 \ + --build-arg BASE_IMAGE_TAG=$(BASE_IMAGE_TAG) \ + . + @echo "Writing image definitions file..." + @printf '{"ImageURI":"%s"}' "$(REPO):$(TAG)" > $(WORKDIR)/imageDetail.json + @echo "Image definitions file created: $(WORKDIR)/imageDetail.json" + +prebuild: + aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin $(REGISTRY-IDS).dkr.ecr.us-west-2.amazonaws.com + docker buildx create --use --name mybuilder --driver docker-container + +buildx: + @echo "Starting Docker Buildx process..." + docker buildx build --builder mybuilder \ + --cache-from type=registry,ref=$(REPO):cache \ + --cache-to mode=max,image-manifest=true,oci-mediatypes=true,type=registry,ref=$(REPO):cache \ + --platform linux/amd64 \ + --build-arg BASE_IMAGE_TAG=$(BASE_IMAGE_TAG) \ + --push --provenance=false \ + $(TARGET_ARG) \ + --tag $(REPO):$(TAG) \ + $(WORKDIR) + @echo "Docker image built and pushed to ECR with tag: $(TAG)" + @echo "Writing image definitions file..." + @printf '{"ImageURI":"%s"}' "$(REPO):$(TAG)" > $(WORKDIR)/imageDetail.json + @echo "Image definitions file created: $(WORKDIR)/imageDetail.json" + +test: + echo "Reading IMAGE_URI from $(WORKDIR)/imageDetail.json..." + IMAGE_URI=$$(jq -r '.ImageURI' $(WORKDIR)/imageDetail.json); \ + echo "Extracting TAG from IMAGE_URI..."; \ + TAG=$$(echo $$IMAGE_URI | sed 's/.*://'); \ + echo "Testing with TAG: $$TAG"; \ + cd ./tests/basic && IMAGE=$(REPO):$$TAG ./run.sh + +push: + @echo "Reading image details from $(WORKDIR)/imageDetail.json..." + @IMAGE_URI=$$(jq -r '.ImageURI' $(WORKDIR)/imageDetail.json); \ + if aws ecr describe-images --repository-name nrel-$(PROJECT_NAME) --image-ids imageTag=$$(basename $$IMAGE_URI) >/dev/null 2>&1; then \ + echo "Image $$IMAGE_URI already exists in ECR. Skipping push."; \ + else \ + echo "Pushing Docker image $$IMAGE_URI..."; \ + docker push $$IMAGE_URI; \ + fi + +deploy: + echo "Reading IMAGE_URI from $(WORKDIR)/imageDetail.json..." + IMAGE_URI=$$(jq -r '.ImageURI' $(WORKDIR)/imageDetail.json); \ + echo "Extracting TAG from IMAGE_URI..."; \ + TAG=$$(echo $$IMAGE_URI | sed 's/.*://'); \ + echo "Deploying with TAG: $$TAG"; \ + echo "Running create-credentials script..."; \ + WORKDIR=$(WORKDIR) $(WORKDIR)/create-credentials.sh; \ + echo "Deploying containers for $(PROJECT_NAME)..."; \ + echo "IMAGE_URI: $$IMAGE_URI"; \ + echo "TAG: $$TAG"; \ + echo "PROJECT_HANDLE: $(PROJECT_HANDLE)"; \ + echo "SERVICE_HANDLE: $(APP_NAME)"; \ + echo "DEPLOY_ENVIRONMENT: $(APPFLEET_RELEASE_NAME)"; \ + DOCKER_TAG1=$$TAG \ + PROJECT_HANDLE=$(PROJECT_HANDLE) \ + SERVICE_HANDLE=$(APP_NAME) \ + DEPLOY_ENVIRONMENT=$(APPFLEET_RELEASE_NAME) \ + docker-compose -f $(WORKDIR)/docker-compose.deploy.yaml up --quiet-pull + +run: + docker run --rm --name $(PROJECT_NAME) $(PORTS) $(VOLUMES) $(ENV) $(REPO):$(TAG) $(CMD) + +clean: + -docker rm -f $(PROJECT_NAME) + +release: build push deploy + +SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(SELF_DIR)docker.mk diff --git a/awscodebuild/buildspec.yml b/awscodebuild/buildspec.yml old mode 100755 new mode 100644 index 4f02096..d9e9c48 --- a/awscodebuild/buildspec.yml +++ b/awscodebuild/buildspec.yml @@ -1,28 +1,65 @@ version: 0.2 env: - variables: - MAKEFILE_PATH: "Makefile" # Default Makefile path, you can change this as needed - ENVIRONMENT: "dev" # You'll set this dynamically based on the build environment in AWS CodeBuild + parameter-store: + NGINX_VER: /nrel/split/split_nginx_version_stable + APPFLEET_DEPLOY_VERSION: /nrel/split/appfleet_docker_version_legacy + exported-variables: + - CODEBUILD_SOURCE_VERSION + - CODEBUILD_BUILD_ID + - CODEBUILD_SOURCE_VERSION_SHORT + - CODEBUILD_BUILD_NUMBER + - PROJECT_HANDLE + - APP_NAME + - APPFLEET_RELEASE_NAME + - APPFLEET_DEPLOY_VERSION + - MAKEFILE_PATH + - BASE_IMAGE_TAG + - CONFIG_FILE_URL + - DIFF_ONLY + phases: - pre_build: + install: commands: - - echo Logging in to Amazon ECR... + - echo "Logging in to Amazon ECR..." - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com - - RELEASE_SHA=$(echo $CODEBUILD_SOURCE_VERSION | head -c 7) - - BRANCH_NAME=$(echo $CODEBUILD_WEBHOOK_HEAD_REF | cut -d'/' -f 3) - build: + - echo "Build started on `date`" + - echo "Checking Docker version..." + - docker --version + - DOCKER_VERSION=$(docker --version | grep -oP '\d+\.\d+\.\d+') + - MIN_VERSION=25.0.0 + - | + if [ "$(printf '%s\n' "$MIN_VERSION" "$DOCKER_VERSION" | sort -V | head -n1)" != "$MIN_VERSION" ]; then + echo "Error: Docker version must be 25.0.0 or higher. Current version: $DOCKER_VERSION" + exit 1 + fi + - echo "Caching Docker images..." + - if [ -d /root/.cache/docker ]; then echo "Docker cache exists"; else echo "Docker cache does not exist"; fi + + pre_build: commands: - - echo Build started on `date` - - echo Building the Docker image... - - make build - post_build: + - CODEBUILD_SOURCE_VERSION_SHORT=$(echo $CODEBUILD_SOURCE_VERSION | head -c 7) + # - echo "Running prebuild step..." + # - make V=1 prebuild + + build: commands: - - echo Build completed on `date` - - echo Pushing the Docker image... - - make push - - echo Writing image definitions file... - - printf '{"ImageURI":"%s"}' > imageDetail.json + - echo "Build started on `date`" + - echo Deploying Docker image to ECS... + - | + if [ -f "$MAKEFILE_PATH" ]; then + make -f "$MAKEFILE_PATH" V=1 \ + release + else + echo "Error: Makefile not found at $MAKEFILE_PATH." + exit 1 + fi + artifacts: files: - imageDetail.json + +cache: + paths: + - '/root/.cache/docker/**/*' + - '/root/.docker' diff --git a/awscodebuild/create-credentials.sh b/awscodebuild/create-credentials.sh new file mode 100755 index 0000000..c77f114 --- /dev/null +++ b/awscodebuild/create-credentials.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Display the WORKDIR location +echo "Using WORKDIR: ${WORKDIR}" + +# Check if WORKDIR is set and accessible +if [[ -n "${WORKDIR}" && ! -d "${WORKDIR}" ]]; then + echo "Error: WORKDIR '${WORKDIR}' does not exist or is not accessible." + exit 1 +fi + +# Determine the output directory for AWS files +aws_dir="${WORKDIR:-.}/.aws" + +# Ensure the output directory exists +mkdir -p "${aws_dir}" + +# Run the aws configure export-credentials command without the --profile option if AWS_PROFILE is not set +if [[ -z "${AWS_PROFILE}" ]]; then + export_credentials=$(aws configure export-credentials) +else + export_credentials=$(aws configure export-credentials --profile "${AWS_PROFILE}") +fi + +# Check if export_credentials variable is populated with values +if [[ -z "${export_credentials}" ]]; then + echo "Error: Unable to retrieve AWS credentials." + exit 1 +fi + +# Extract access key ID, secret access key, and session token using jq +access_key_id=$(echo "$export_credentials" | jq -r '.AccessKeyId') +secret_access_key=$(echo "$export_credentials" | jq -r '.SecretAccessKey') +session_token=$(echo "$export_credentials" | jq -r '.SessionToken') + +# Check if any of the variables are empty and exit if so +if [[ -z "$access_key_id" || -z "$secret_access_key" || -z "$session_token" ]]; then + echo "Error: One or more AWS credential variables are empty." + exit 1 +fi + +# Write the credentials to the specified credentials file +cat << EOF > "${aws_dir}/credentials" +[default] +aws_access_key_id = $access_key_id +aws_secret_access_key = $secret_access_key +aws_session_token = $session_token +EOF + +# Write the default region to the config file in the specified directory +cat << EOF > "${aws_dir}/config" +[default] +region = us-west-2 +EOF + +echo "AWS credentials file created at ${aws_dir}/credentials" +echo "AWS config file created at ${aws_dir}/config" diff --git a/awscodebuild/docker-compose.deploy.yaml b/awscodebuild/docker-compose.deploy.yaml new file mode 100644 index 0000000..76c6510 --- /dev/null +++ b/awscodebuild/docker-compose.deploy.yaml @@ -0,0 +1,17 @@ +--- + +services: + appfleet-pipeline: + image: + 991404956194.dkr.ecr.us-west-2.amazonaws.com/nrel-appfleet-pipeline:${APPFLEET_DEPLOY_VERSION:-0.0.12-1.0.0-alpine-3c676c3} + environment: + - DOCKER_TAG1=${DOCKER_TAG1} + - DOCKER_TAG2=${DOCKER_TAG2:-1.25.3.1} + - DEPLOY_ENVIRONMENT=${APPFLEET_RELEASE_NAME:-dev} + - WORKER_TAG=${WORKER_TAG} + - PROJECT_HANDLE=${PROJECT_HANDLE} + - SERVICE_HANDLE=${APP_NAME} + - SLEEP_ONLY=${SLEEP_ONLY:-False} + - DIFF_ONLY=${DIFF_ONLY:-False} + volumes: + - ./.aws:/root/.aws:ro diff --git a/awscodebuild/docker.mk b/awscodebuild/docker.mk new file mode 100644 index 0000000..a929688 --- /dev/null +++ b/awscodebuild/docker.mk @@ -0,0 +1,75 @@ +include awscodebuild/.env + +default: up + +## help : Print commands help. +.PHONY: help +ifneq (,$(wildcard docker.mk)) +help : docker.mk + @sed -n 's/^##//p' $< +else +help : Makefile + @sed -n 's/^##//p' $< +endif + +## up : Start up containers. +.PHONY: up +up: + @echo "Starting up containers for $(PROJECT_NAME)..." + docker-compose pull + docker-compose up -d --remove-orphans --build +## down : Stop containers. +.PHONY: down +down: stop + +## start : Start containers without updating. +.PHONY: start +start: + @echo "Starting containers for $(PROJECT_NAME) from where you left off..." + @docker-compose start + +## stop : Stop containers. +.PHONY: stop +stop: + @echo "Stopping containers for $(PROJECT_NAME)..." + @docker-compose stop + +## prune : Remove containers and their volumes. +## You can optionally pass an argument with the service name to prune single container +## prune mariadb : Prune `mariadb` container and remove its volumes. +## prune mariadb solr : Prune `mariadb` and `solr` containers and remove their volumes. +.PHONY: prune +prune: + @echo "Removing containers for $(PROJECT_NAME)..." + @docker-compose down -v $(filter-out $@,$(MAKECMDGOALS)) + +## ps : List running containers. +.PHONY: ps +ps: + @docker ps --filter name='$(PROJECT_NAME)*' + +## shell : Access `html` container via shell. +## You can optionally pass an argument with a service name to open a shell on the specified container +.PHONY: shell +shell: + docker exec -ti -e COLUMNS=$(shell tput cols) -e LINES=$(shell tput lines) $(shell docker ps --filter name='$(PROJECT_NAME)_$(or $(filter-out $@,$(MAKECMDGOALS)), 'nginx')' --format "{{ .ID }}") sh + +## composer : Executes `composer` command in a specified `COMPOSER_ROOT` directory (default is `/var/www/html`). +## To use "--flag" arguments include them in quotation marks. +## For example: make composer "update drupal/core --with-dependencies" +.PHONY: composer +composer: + docker exec $(shell docker ps --filter name='^/$(PROJECT_NAME)_nginx' --format "{{ .ID }}") composer --working-dir=$(COMPOSER_ROOT) $(filter-out $@,$(MAKECMDGOALS)) + + +## logs : View containers logs. +## You can optinally pass an argument with the service name to limit logs +## logs html : View `html` container logs. +## logs nginx html : View `nginx` and `html` containers logs. +.PHONY: logs +logs: + @docker-compose logs -f $(filter-out $@,$(MAKECMDGOALS)) + +# https://stackoverflow.com/a/6273809/1826109 +%: + @: diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..e4d3b87 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,17 @@ + version: '3.9' + services: + + dashboard: + build: + context: ./ + container_name: tap-api + hostname: tap-api + restart: always + ports: + - 8080:80 + networks: + - network + + networks: + network: + driver: bridge diff --git a/docs/index.html b/docs/index.html index 82ac436..c363d74 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,22 +1,36 @@ - - Loading... + WindWatts API + - - - - - + + + + + + + + + + + + @@ -83,8 +98,10 @@

{{name}}

+ + + + + + + +
-
-
-
+
+
+
@@ -687,6 +1042,6 @@

{{__ compare.key}}

- + diff --git a/dw_tap b/dw_tap index 8854790..af0ace9 160000 --- a/dw_tap +++ b/dw_tap @@ -1 +1 @@ -Subproject commit 8854790dff513dbe535c5136f195d1f41fdc04f8 +Subproject commit af0ace923fef21208df2f8551d9508d78b60f6e3 diff --git a/html_maker.py b/html_maker.py new file mode 100644 index 0000000..e025a1b --- /dev/null +++ b/html_maker.py @@ -0,0 +1,394 @@ + + +def summary_to_html(lat, lon, closest_height, powercurve, mean, ws_level, kwh_produced_avg_year, version="latest"): + + if version == "latest": + # This version is based on:https://codepen.io/mrsahar/pen/yOVGBQ + # (one of tabled styles listed at: https://devdevout.com/css/css-tables, authored by: Sahar Ali Raza) + + html = """ +

Analysis presented below was performed using + summary data from NREL's 20-year + NREL's WTK-LED dataset using the following options: + +

+
+
    +
  • Selected location (lat, lon): %.5f, %.5f
  • +
  • Selected hub height: %.1f meters
  • +
  • Selected power curve: %s
  • +
+
+
+
+ + +
+
+ + +
+ + +
+ + +
+ + +
+
+ Average wind speed +
+ + +
+ + + +
+ + %.2f + +
+ + +
+ + + +
+
    +
  • m/s, meters per second
  • +
+
+ + +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+
+ Wind Resource +
+ + +
+ + + +
+ + level: + %s + +
+ + +
+ + + +
+
    +
  • based on average wind speed
  • +
+
+ + + +
+ + +
+
+ + +
+ + +
+ + +
+ + +
+
+ Energy Production +
+ + +
+ + + +
+ + %s + +
+ + +
+ + + +
+
    +
  • Average kWh/year
  • +
+
+ + +
+ + +
+
+ + +
+
+
+
+

+ + Disclaimer: This summary represents a PRELIMINARY analysis. + Research conducted at national laboratories suggests that multiple models should be used for + more thorough analysis. + Reach out to a qualified installer for a refined estimate. +

+
+ """ % (lat, lon, closest_height, powercurve, mean, ws_level, kwh_produced_avg_year) + return html + + elif version == "v2": + html = """ +

Analysis presented below was done using NREL's WTK-LED dataset* and its 12x24 summaries for 2001-2020 at %d meters.

+
+ + + + + + + + + +
20-year wind speed average, m/s:
%.2f
+
+

* Disclaimer: Research conducted at national laboratories suggests that multiple models should be used for + more thorough analysis.

+ """ % (mean, closest_height) + return html + + elif version == "v1": + html = """ + + + + + + + + + + + + +
All-time wind speed average, m/s:
%.2f
estimated using 12x24 summaries from WTK-LED
for 2001-2020 at %d meters.
+ """ % (mean, closest_height) + + html += """ +
+ + + + + + + + + + + + + +
Wind speed average for
lowest year (%d):
Wind speed average for
median year (%d):
Wind speed average for
highest year (%d):
%.2f%.2f%.2f
+ """ % (2008, 2010, 2012, mean*0.9, mean*1.0, mean*1.1) + + return html + + else: + return "" + + +def windresource_to_html(mean, ws_level, moderate_resource_thresh_ms, high_resource_thresh_ms, closest_height, version="latest", windrose_plot_name=""): + + if version == "latest": + # This version is based on the latest in summary_to_html() + + html = """ +

The wind resource is characterized by wind speed and wind direction. + These are both illustrated by a "wind rose" which shows how fast the wind blows from each wind direction throughout the year. +

+
+

+ Understanding where the wind comes from and how fast the wind blows are critical to placing the wind turbine + in a location that will not be affected by nearby structures and trees. +

+
+ """ + + if windrose_plot_name: + html += """ +
+
+ + + + +
+
+
+
+ """ % windrose_plot_name + + html += """ +

+ Whether or not a specific project site is a good candidate for a wind turbine is based on several key factors including available financial incentives, + the cost of power in the area, cost to install the turbine as well as the wind resource. In the absence of detailed project economics, + it is illustrative to understand whether the wind speed is generally "low", "moderate", or "high" -- which correlates directly with low, moderate or high turbine performance. +

+
+ """ + + html += """ +
+
+
+ + +
+
+ + +
+ + +
+ + +
+ + +
+
+ Wind Resource +
+ + +
+ + + +
+ + level: + %s + +
+ + +
+ + + +
+
    +
  • Details:
  • +
  • Estimated average wind speed is at %.2f m/s.
  • +
  • Low wind resource referts to wind speeds below %.2f m/s.
  • +
  • Moderate wind resource refers to wind speeds between %.2f and %.2f m/s.
  • +
  • High wind resource refers to wind speeds above %.2f m/s.
  • +
+
+ + +
+ + +
+ +
+ + +
+
+
+ + """ % (ws_level, mean, moderate_resource_thresh_ms, moderate_resource_thresh_ms, high_resource_thresh_ms, high_resource_thresh_ms) #, closest_height) + return html + else: + return "" + + +def energyproduction_to_html(monthly_df, yearly_df): + monthly_html = monthly_df.to_html(classes="estimates_by_month_table") # classes="detailed_yearly_table") + yearly_html = yearly_df.to_html(classes="estimates_by_year_table") #classes="detailed_yearly_table") + + html = """ +

+ The wind resource, and by extension the energy production, varies month to month and year to year. + It is important to understand the average characteristics as well as the variability you can expect to see from your wind turbine on any given year. +

+
+ + + + + + + + + + + + + +
+ + Estimates by month: + + + Estimates by year: +
%s%s
+ """ % (monthly_html, yearly_html) + return html diff --git a/misc/test-dev.sh b/misc/test-dev.sh new file mode 100644 index 0000000..1427dfc --- /dev/null +++ b/misc/test-dev.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +curl -X GET -H "Content-Type: application/json" -d @test.json https://dw-tap-dev.stratus.nrel.gov/batch diff --git a/misc/test-local.sh b/misc/test-local.sh new file mode 100644 index 0000000..8d39a19 --- /dev/null +++ b/misc/test-local.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +curl -X GET -H "Content-Type: application/json" -d @test.json localhost:8080/batch diff --git a/misc/test-windwatts-dev.ipynb b/misc/test-windwatts-dev.ipynb new file mode 100644 index 0000000..0c329be --- /dev/null +++ b/misc/test-windwatts-dev.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "eddd2b69-1257-476a-bdd1-47cb122a414c", + "metadata": {}, + "source": [ + "### Required setup:\n", + "\n", + "`pip install httpx[http2]`" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "963f7b24-d4d8-4bea-a0b8-08a331564978", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b'[\\n \"{\\\\\"data\\\\\": \\\\\"mohr,precipitation_0m,pressure_0m,pressure_100m,pressure_200m,pressure_500m,temperature_1000m,temperature_100m,temperature_200m,temperature_2m,temperature_300m,temperature_30m,temperature_40m,temperature_500m,temperature_60m,temperature_80m,winddirection_1000m,winddirection_100m,winddirection_10m,winddirection_120m,winddirection_140m,winddirection_160m,winddirection_180m,winddirection_200m,winddirection_250m,winddirection_300m,winddirection_30m,winddirection_40m,winddirection_60m,winddirection_80m,winddirection_500m,windspeed_1000m,windspeed_100m,windspeed_10m,windspeed_120m,windspeed_140m,windspeed_160m,windspeed_180m,windspeed_200m,windspeed_250m,windspeed_300m,windspeed_40m,windspeed_30m,windspeed_500m,windspeed_60m,windspeed_80m,month,h,year\\\\\\\\n101,36.086006,82130.0,81110.0,80110.0,77160.0,-4.71,1.68,0.94,0.89,0.2,1.9,1.95,-1.28,1.93,1.82,303.98,306.05,307.09,305.82,306.08,310.47,315.81,316.41,317.03,313.01,302.74,304.28,305.85,306.2,307.2,10.32,6.82,2.05,6.69,6.59,6.53,6.51,6.51,6.62,6.84,5.23,4.34,7.81,6.33,6.71,1,1,2001\\\\\\\\n102,36.138237,82150.0,81120.0,80120.0,77170.0,-4.67,1.35,0.71,0.19,0.0,1.39,1.51,-1.55,1.54,1.46,307.96,298.22,294.68,301.85,305.56,308.6,311.4,313.84,318.55,318.35,300.23,300.97,297.22,296.12,318.29,9.99,6.97,2.31,7.09,7.21,7.36,7.46,7.51,7.78,8.06,5.84,4.99,9.31,6.59,6.82,1,2,2001\\\\\\\\n103,36.220783,82160.0,81140.0,80130.0,77180.0,-4.78,1.12,0.58,-0.16,-0.18,1.04,1.17,-1.64,1.23,1.19,308.01,302.44,263.83,304.37,306.83,308.75,310.44,311.32,310.97,310.06,283.82,291.39,299.61,301.53,309.51,9.35,8.55,2.39,8.49,8.41,8.31,8.2,8.11,8.0,7.95,6.82,5.77,8.12,7.84,8.29,1,3,2001\\\\\\\\n104,36.305893,82180.0,81160.0,80150.0,77200.0,-4.92,0.91,0.35,-0.45,-0.32,0.83,1.0,-1.72,1.06,1.0,302.66,290.73,266.14,294.32,297.95,300.77,302.96,304.83,308.54,306.67,280.3,281.48,283.78,287.52,307.11,10.23,7.74,2.38,7.85,7.95,8.04,8.12,8.19,8.23,8.33,6.8,5.84,8.68,7.45,7.59,1,4,2001\\\\\\\\n105,36.36418,82200.0,81170.0,80160.0,77200.0,-4.91,0.71,0.2\n" + ] + } + ], + "source": [ + "import httpx\n", + "import json\n", + "\n", + "def read_json_from_file(file_path):\n", + " try:\n", + " with open(file_path, 'r') as f:\n", + " data = json.load(f)\n", + " return data\n", + " except FileNotFoundError:\n", + " print(f\"Error: File not found at {file_path}\")\n", + " return None\n", + " except json.JSONDecodeError:\n", + " print(f\"Error: Invalid JSON format in {file_path}\")\n", + " return None\n", + "\n", + "url = \"https://dw-tap-dev.stratus.nrel.gov/batch\"\n", + "file_path = \"test.json\" \n", + "json_data = read_json_from_file(file_path)\n", + "\n", + "async with httpx.AsyncClient(http2=True) as client:\n", + "\n", + " response = await client.request(\n", + " method=\"GET\",\n", + " url=url,\n", + " json=json_data,\n", + " timeout=None\n", + " )\n", + "\n", + "# print only the top of the very long output\n", + "print(str(response.content)[:2000])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43f10884-0326-49d8-be48-ac382c2c92c4", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/misc/test.json b/misc/test.json new file mode 100644 index 0000000..a171d16 --- /dev/null +++ b/misc/test.json @@ -0,0 +1,31 @@ +{ + "windwatts-api-request-windspeed" : [ + { + "lat": 39.74, + "lon": -105.16, + "height": 40, + "dataset": "wtk-led-1224" + }, + { + "lat": 39.91, + "lon": -105.21, + "height": 60, + "dataset": "wtk-led-1224" + } + ], + + "windwatts-api-request-windenergy" : [ + { + "lat": 39.74, + "lon": -105.16, + "height": 40, + "dataset": "wtk-led-1224" + }, + { + "lat": 39.91, + "lon": -105.21, + "height": 60, + "dataset": "wtk-led-1224" + } + ] +} diff --git a/obs/met_tower_obs_summary.geojson b/obs/met_tower_obs_summary.geojson new file mode 100644 index 0000000..a7605f7 --- /dev/null +++ b/obs/met_tower_obs_summary.geojson @@ -0,0 +1,342 @@ +{ +"type": "FeatureCollection", +"name": "met_tower_obs_summary", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC::CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "site_id": "oh_toledo_26", "wind_speed_mean": 3.7022671214105181, "time_start": "2006-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 26, "n_samples": 136375, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.473, 41.694 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_eastbay_11", "wind_speed_mean": 2.7280471184829116, "time_start": "2009-05-19T15:00:00", "time_end": "2021-12-31T22:00:00", "height": 11, "n_samples": 95512, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.426, 27.929 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway2_10", "wind_speed_mean": 6.1637329231251927, "time_start": "2003-10-28T06:00:00", "time_end": "2008-06-05T10:50:00", "height": 10, "n_samples": 242238, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.52, 39.4 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway2_49", "wind_speed_mean": 8.0027098305444326, "time_start": "2003-10-28T06:00:00", "time_end": "2008-06-05T10:50:00", "height": 49, "n_samples": 242238, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.52, 39.4 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway2_30", "wind_speed_mean": 7.3630357930405088, "time_start": "2003-10-28T06:00:00", "time_end": "2008-06-05T10:50:00", "height": 30, "n_samples": 242238, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.52, 39.4 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar5_10", "wind_speed_mean": 5.4912872714169891, "time_start": "2001-11-25T21:00:00", "time_end": "2007-03-14T05:00:00", "height": 10, "n_samples": 45243, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.503, 43.666 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar5_49", "wind_speed_mean": 7.4577415298797503, "time_start": "2001-11-25T21:00:00", "time_end": "2007-03-14T05:00:00", "height": 49, "n_samples": 45243, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.503, 43.666 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar5_30", "wind_speed_mean": 6.6567937073181715, "time_start": "2001-11-25T21:00:00", "time_end": "2007-03-14T05:00:00", "height": 30, "n_samples": 45243, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.503, 43.666 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway3_39", "wind_speed_mean": 7.775794990135287, "time_start": "2003-10-29T17:30:00", "time_end": "2005-03-13T15:50:00", "height": 39, "n_samples": 72135, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.582, 39.441 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway3_10", "wind_speed_mean": 6.2290676069347723, "time_start": "2003-10-29T17:30:00", "time_end": "2005-03-13T15:50:00", "height": 10, "n_samples": 72135, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.582, 39.441 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway3_30", "wind_speed_mean": 7.4347558965816658, "time_start": "2003-10-29T17:30:00", "time_end": "2005-03-13T15:50:00", "height": 30, "n_samples": 72135, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.582, 39.441 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_tonzi_23", "wind_speed_mean": 2.1610619794218517, "time_start": "2007-01-01T00:00:00", "time_end": "2008-01-01T07:00:00", "height": 23, "n_samples": 16354, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -120.966, 38.987 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_burnsharbor_10", "wind_speed_mean": 5.0949197732282254, "time_start": "2006-12-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 108124, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.147, 41.646 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_sabinepass_13", "wind_speed_mean": 5.4958170538936439, "time_start": "2013-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 13, "n_samples": 69320, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -93.842, 29.689 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_grandportage_50", "wind_speed_mean": 7.2855806934635083, "time_start": "2007-03-09T06:30:00", "time_end": "2009-07-29T17:50:00", "height": 50, "n_samples": 125781, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.7585, 47.9744 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_holland_10", "wind_speed_mean": 5.5412616606839427, "time_start": "2011-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 91724, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -86.213, 42.773 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_manisteeharbor_12", "wind_speed_mean": 5.3119312285356637, "time_start": "2010-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 12, "n_samples": 94397, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -86.342, 44.251 ] } }, +{ "type": "Feature", "properties": { "site_id": "al_cedarpoint_12", "wind_speed_mean": 4.8678116756508771, "time_start": "2012-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 12, "n_samples": 71196, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.14, 30.308 ] } }, +{ "type": "Feature", "properties": { "site_id": "wi_saxonharbor_10", "wind_speed_mean": 3.3761422100839531, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 112918, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.437, 46.563 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_bayougauche_31", "wind_speed_mean": 2.00935017616542, "time_start": "2006-01-01T01:00:00", "time_end": "2021-12-31T23:00:00", "height": 31, "n_samples": 129882, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.42, 29.789 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway4_10", "wind_speed_mean": 5.6595822640820881, "time_start": "2003-11-01T17:10:00", "time_end": "2008-12-16T05:50:00", "height": 10, "n_samples": 266303, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.584, 39.415 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway4_49", "wind_speed_mean": 7.7003308210996186, "time_start": "2003-11-01T17:10:00", "time_end": "2008-12-16T05:50:00", "height": 49, "n_samples": 266303, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.584, 39.415 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway4_30", "wind_speed_mean": 6.9908479667818773, "time_start": "2003-11-01T17:10:00", "time_end": "2008-12-16T05:50:00", "height": 30, "n_samples": 266303, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.584, 39.415 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway6_10", "wind_speed_mean": 5.4255235001590689, "time_start": "2004-11-13T14:30:00", "time_end": "2008-06-03T00:50:00", "height": 10, "n_samples": 186831, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.553, 39.417 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway6_49", "wind_speed_mean": 7.677448742878588, "time_start": "2004-11-13T14:30:00", "time_end": "2008-06-03T00:50:00", "height": 49, "n_samples": 186831, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.553, 39.417 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway6_30", "wind_speed_mean": 7.0183673286663213, "time_start": "2004-11-13T14:30:00", "time_end": "2008-06-03T00:50:00", "height": 30, "n_samples": 186831, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.553, 39.417 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_stclairshores_10", "wind_speed_mean": 4.0220991966468729, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 115856, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.877, 42.471 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_galvestonbay_12", "wind_speed_mean": 5.9206342321501726, "time_start": "2012-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 12, "n_samples": 86071, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -94.725, 29.357 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_tillamook_18", "wind_speed_mean": 4.8708730578343271, "time_start": "2007-01-01T00:00:00", "time_end": "2015-06-23T16:00:00", "height": 18, "n_samples": 66525, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -123.919, 45.555 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion4_40", "wind_speed_mean": 7.0375117273695409, "time_start": "2006-12-13T18:00:00", "time_end": "2007-06-22T17:50:00", "height": 40, "n_samples": 27504, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.647, 32.389 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion4_58", "wind_speed_mean": 7.6487865263157895, "time_start": "2006-12-13T18:00:00", "time_end": "2007-06-22T17:50:00", "height": 58, "n_samples": 27504, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.647, 32.389 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion4_20", "wind_speed_mean": 6.1427342329766716, "time_start": "2006-12-13T18:00:00", "time_end": "2007-06-22T17:50:00", "height": 20, "n_samples": 27504, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.647, 32.389 ] } }, +{ "type": "Feature", "properties": { "site_id": "wa_hanford_25", "wind_speed_mean": 4.1920517831915438, "time_start": "2007-01-01T08:15:00", "time_end": "2020-01-01T08:00:00", "height": 25, "n_samples": 455797, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -119.359174, 46.429654 ] } }, +{ "type": "Feature", "properties": { "site_id": "wa_hanford_10", "wind_speed_mean": 3.6797662657384982, "time_start": "2007-01-01T08:15:00", "time_end": "2020-01-01T08:00:00", "height": 10, "n_samples": 455797, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -119.359174, 46.429654 ] } }, +{ "type": "Feature", "properties": { "site_id": "wa_hanford_60", "wind_speed_mean": 4.6739507981867163, "time_start": "2007-01-01T08:15:00", "time_end": "2020-01-01T08:00:00", "height": 60, "n_samples": 455797, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -119.359174, 46.429654 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_grandtraverse_16", "wind_speed_mean": 5.2556613014913278, "time_start": "2007-01-01T02:00:00", "time_end": "2021-12-31T23:00:00", "height": 16, "n_samples": 119885, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -85.55, 45.211 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_packerychannel_11", "wind_speed_mean": 5.2457469042029183, "time_start": "2009-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 111386, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.237, 27.634 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_sevenmile_30", "wind_speed_mean": 7.1344717753673672, "time_start": "2002-01-29T14:00:00", "time_end": "2018-05-01T14:50:00", "height": 30, "n_samples": 851151, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -121.2668, 45.633598 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_sevenmile_15", "wind_speed_mean": 6.7105908868126329, "time_start": "2002-01-29T14:00:00", "time_end": "2018-05-01T14:50:00", "height": 15, "n_samples": 851151, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -121.2668, 45.633598 ] } }, +{ "type": "Feature", "properties": { "site_id": "ma_blandford_40", "wind_speed_mean": 4.4471319193080978, "time_start": "2007-01-01T05:00:00", "time_end": "2009-01-01T04:50:00", "height": 40, "n_samples": 105264, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.96826, 42.22327 ] } }, +{ "type": "Feature", "properties": { "site_id": "ma_blandford_60", "wind_speed_mean": 5.2479366123689823, "time_start": "2007-01-01T05:00:00", "time_end": "2009-01-01T04:50:00", "height": 60, "n_samples": 105264, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.96826, 42.22327 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar6_10", "wind_speed_mean": 4.8430862877843097, "time_start": "2002-01-01T06:00:00", "time_end": "2006-05-26T22:00:00", "height": 10, "n_samples": 36958, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.608, 43.591 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar6_49", "wind_speed_mean": 7.1453090606222869, "time_start": "2002-01-01T06:00:00", "time_end": "2006-05-26T22:00:00", "height": 49, "n_samples": 36958, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.608, 43.591 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar6_30", "wind_speed_mean": 6.2511457841284832, "time_start": "2002-01-01T06:00:00", "time_end": "2006-05-26T22:00:00", "height": 30, "n_samples": 36958, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.608, 43.591 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_calumetharbor_10", "wind_speed_mean": 3.7967185539970347, "time_start": "2005-01-01T03:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 144911, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.538, 41.73 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain3_57", "wind_speed_mean": 8.436193105551812, "time_start": "2009-02-25T23:20:00", "time_end": "2013-05-28T17:00:00", "height": 57, "n_samples": 223595, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.272, 34.435 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain3_53", "wind_speed_mean": 8.2706636864964675, "time_start": "2009-02-25T23:20:00", "time_end": "2013-05-28T17:00:00", "height": 53, "n_samples": 223595, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.272, 34.435 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain3_38", "wind_speed_mean": 7.8437920979226332, "time_start": "2009-02-25T23:20:00", "time_end": "2013-05-28T17:00:00", "height": 38, "n_samples": 223595, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.272, 34.435 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain3_23", "wind_speed_mean": 7.2707881846702378, "time_start": "2009-02-25T23:20:00", "time_end": "2013-05-28T17:00:00", "height": 23, "n_samples": 223595, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.272, 34.435 ] } }, +{ "type": "Feature", "properties": { "site_id": "al_middlebay_14", "wind_speed_mean": 5.9469776542065711, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 14, "n_samples": 118033, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.011, 30.437 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail6_59", "wind_speed_mean": 7.3985978272021509, "time_start": "2010-03-17T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 59, "n_samples": 88239, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.989, 40.485 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail6_20", "wind_speed_mean": 5.8383497998856493, "time_start": "2010-03-17T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 20, "n_samples": 88239, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.989, 40.485 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail6_39", "wind_speed_mean": 6.7519680326553075, "time_start": "2010-03-17T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 39, "n_samples": 88239, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.989, 40.485 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_toledolight_15", "wind_speed_mean": 6.6276322323558974, "time_start": "2006-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 15, "n_samples": 84833, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.194, 41.826 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar2_10", "wind_speed_mean": 5.3212183947471585, "time_start": "2001-08-17T15:00:00", "time_end": "2006-05-25T00:00:00", "height": 10, "n_samples": 41598, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.634, 43.602 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar2_49", "wind_speed_mean": 7.2961988261188573, "time_start": "2001-08-17T15:00:00", "time_end": "2006-05-25T00:00:00", "height": 49, "n_samples": 41598, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.634, 43.602 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar2_30", "wind_speed_mean": 6.6391387501224406, "time_start": "2001-08-17T15:00:00", "time_end": "2006-05-25T00:00:00", "height": 30, "n_samples": 41598, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.634, 43.602 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_niagara_10", "wind_speed_mean": 4.1228942724209858, "time_start": "2007-08-15T16:00:00", "time_end": "2019-03-11T23:00:00", "height": 10, "n_samples": 95689, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -79.064, 43.262 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_terrebonnebay_14", "wind_speed_mean": 4.7750146255850234, "time_start": "2008-07-01T00:00:00", "time_end": "2012-12-29T07:00:00", "height": 14, "n_samples": 20789, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.608, 29.187 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_aripeka_11", "wind_speed_mean": 2.4931562807247527, "time_start": "2008-07-22T22:00:00", "time_end": "2021-08-24T05:00:00", "height": 11, "n_samples": 96675, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.667, 28.433 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_matagordabay_12", "wind_speed_mean": 5.9979746455629739, "time_start": "2017-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 12, "n_samples": 41052, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -96.327, 28.422 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_losangelespierj_31", "wind_speed_mean": 3.5285911602209943, "time_start": "2014-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 31, "n_samples": 64165, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.186, 33.733 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_menominee_11", "wind_speed_mean": 4.555928556738543, "time_start": "2006-10-03T14:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 129671, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.59, 45.096 ] } }, +{ "type": "Feature", "properties": { "site_id": "co_niwotridge_26", "wind_speed_mean": 4.6927908104363887, "time_start": "2007-01-01T00:00:00", "time_end": "2008-01-01T06:00:00", "height": 26, "n_samples": 17447, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -105.5464, 39.305 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_gravellyshoal_25", "wind_speed_mean": 6.1517111159124864, "time_start": "2005-12-13T15:00:00", "time_end": "2021-12-31T23:00:00", "height": 25, "n_samples": 107500, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.537, 44.018 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway5_39", "wind_speed_mean": 7.6176443008181725, "time_start": "2003-10-18T15:00:00", "time_end": "2004-11-07T07:50:00", "height": 39, "n_samples": 55542, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.553, 39.419 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway5_10", "wind_speed_mean": 5.9977983307015554, "time_start": "2003-10-18T15:00:00", "time_end": "2004-11-07T07:50:00", "height": 10, "n_samples": 55542, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.553, 39.419 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway5_30", "wind_speed_mean": 7.2540283769711396, "time_start": "2003-10-18T15:00:00", "time_end": "2004-11-07T07:50:00", "height": 30, "n_samples": 55542, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.553, 39.419 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain_24", "wind_speed_mean": 6.9619044343243193, "time_start": "2008-08-28T01:00:00", "time_end": "2013-09-30T20:00:00", "height": 24, "n_samples": 44636, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.32626, 34.43335 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain_58", "wind_speed_mean": 8.3130961983505678, "time_start": "2008-08-28T01:00:00", "time_end": "2013-09-30T20:00:00", "height": 58, "n_samples": 44636, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.32626, 34.43335 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain_53", "wind_speed_mean": 8.1422212509631553, "time_start": "2008-08-28T01:00:00", "time_end": "2013-09-30T20:00:00", "height": 53, "n_samples": 44636, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.32626, 34.43335 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain_38", "wind_speed_mean": 7.6440824508252518, "time_start": "2008-08-28T01:00:00", "time_end": "2013-09-30T20:00:00", "height": 38, "n_samples": 44636, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.32626, 34.43335 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar7_10", "wind_speed_mean": 5.1980958985085479, "time_start": "2001-11-26T15:00:00", "time_end": "2007-04-17T13:00:00", "height": 10, "n_samples": 45077, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.66, 43.63 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar7_49", "wind_speed_mean": 7.447614264886254, "time_start": "2001-11-26T15:00:00", "time_end": "2007-04-17T13:00:00", "height": 49, "n_samples": 45077, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.66, 43.63 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar7_30", "wind_speed_mean": 6.6575477663542895, "time_start": "2001-11-26T15:00:00", "time_end": "2007-04-17T13:00:00", "height": 30, "n_samples": 45077, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.66, 43.63 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_southwestpass_38", "wind_speed_mean": 6.3495295939025862, "time_start": "2000-01-01T00:00:00", "time_end": "2019-11-30T23:00:00", "height": 38, "n_samples": 135497, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.428, 28.905 ] } }, +{ "type": "Feature", "properties": { "site_id": "wa_destructionisland_31", "wind_speed_mean": 6.2307812259904054, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 31, "n_samples": 177547, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -124.485, 47.675 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail5_40", "wind_speed_mean": 6.8702279417569905, "time_start": "2010-03-19T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 40, "n_samples": 88762, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.021, 40.46 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail5_59", "wind_speed_mean": 7.4436512000360917, "time_start": "2010-03-19T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 59, "n_samples": 88762, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.021, 40.46 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail5_20", "wind_speed_mean": 6.0497602156480159, "time_start": "2010-03-19T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 20, "n_samples": 88762, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.021, 40.46 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marblehead_10", "wind_speed_mean": 3.7469371634297692, "time_start": "2006-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 135969, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.731, 41.544 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_conneaut_12", "wind_speed_mean": 6.1093479560688531, "time_start": "2007-08-11T18:00:00", "time_end": "2019-07-04T20:00:00", "height": 12, "n_samples": 68392, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -80.556, 41.981 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_whiteshoal_43", "wind_speed_mean": 6.7913862562675709, "time_start": "2016-01-01T17:00:00", "time_end": "2021-12-31T23:00:00", "height": 43, "n_samples": 36256, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -85.135, 45.842 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_grandisleblocks_10", "wind_speed_mean": 6.1794400512930112, "time_start": "2015-07-07T19:00:00", "time_end": "2017-10-07T22:00:00", "height": 10, "n_samples": 14394, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.978, 29.101 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_troutdale_30", "wind_speed_mean": 3.2262248802060038, "time_start": "2010-02-01T08:00:00", "time_end": "2018-05-01T06:50:00", "height": 30, "n_samples": 433530, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -122.4017, 45.558324 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_stjohnsriver_10", "wind_speed_mean": 2.7763976576081983, "time_start": "2015-01-01T00:00:00", "time_end": "2017-09-11T13:00:00", "height": 10, "n_samples": 23315, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -81.692, 30.192 ] } }, +{ "type": "Feature", "properties": { "site_id": "ma_buzzardsbay_25", "wind_speed_mean": 7.7087571720581538, "time_start": "2000-01-01T00:00:00", "time_end": "2021-11-21T12:00:00", "height": 25, "n_samples": 183052, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -71.033, 41.397 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_bigcarlospass_17", "wind_speed_mean": 3.8671772668730995, "time_start": "2008-07-26T16:00:00", "time_end": "2021-12-31T23:00:00", "height": 17, "n_samples": 73335, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -81.881, 26.404 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_portsanluis_14", "wind_speed_mean": 2.6374063745445611, "time_start": "2009-05-07T16:00:00", "time_end": "2021-12-31T23:00:00", "height": 14, "n_samples": 108552, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -120.754, 35.169 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar8_10", "wind_speed_mean": 5.5172329007708063, "time_start": "2003-12-11T06:00:00", "time_end": "2006-05-04T15:00:00", "height": 10, "n_samples": 21010, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.582, 43.63 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar8_50", "wind_speed_mean": 7.5294340272651219, "time_start": "2003-12-11T06:00:00", "time_end": "2006-05-04T15:00:00", "height": 50, "n_samples": 21010, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.582, 43.63 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar8_30", "wind_speed_mean": 6.9105887258990331, "time_start": "2003-12-11T06:00:00", "time_end": "2006-05-04T15:00:00", "height": 30, "n_samples": 21010, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.582, 43.63 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_passageisland_16", "wind_speed_mean": 6.8390389060508525, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 16, "n_samples": 185644, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.366, 48.223 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_frenierlanding_10", "wind_speed_mean": 3.0644384043161934, "time_start": "2015-05-05T22:00:00", "time_end": "2021-09-12T16:00:00", "height": 10, "n_samples": 52047, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.422, 30.106 ] } }, +{ "type": "Feature", "properties": { "site_id": "wi_kenosha_20", "wind_speed_mean": 5.2244293169956881, "time_start": "2006-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 20, "n_samples": 135995, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.809, 42.589 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_tawaspoint_10", "wind_speed_mean": 3.3921391841722968, "time_start": "2016-08-15T13:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 43548, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.449, 44.254 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_southbassisland_21", "wind_speed_mean": 5.9805277204290253, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 21, "n_samples": 179354, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.841, 41.629 ] } }, +{ "type": "Feature", "properties": { "site_id": "ct_newlondon_20", "wind_speed_mean": 5.9949330278463169, "time_start": "2005-01-01T00:00:00", "time_end": "2020-01-08T11:00:00", "height": 20, "n_samples": 82662, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.077, 41.306 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_rolloverpass_11", "wind_speed_mean": 4.6916024878425482, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 110167, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -94.513, 29.515 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_tampa_23", "wind_speed_mean": 2.5184746650032861, "time_start": "2008-10-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 23, "n_samples": 90945, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.433, 27.933 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_sanfrancisco_16", "wind_speed_mean": 2.7707894107474655, "time_start": "2014-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 16, "n_samples": 57868, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -122.393, 37.798 ] } }, +{ "type": "Feature", "properties": { "site_id": "ms_baywaveland_10", "wind_speed_mean": 4.0991333732000648, "time_start": "2009-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 108553, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.326, 30.326 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_spectaclereef_30", "wind_speed_mean": 7.3544338875692796, "time_start": "2016-01-01T00:00:00", "time_end": "2021-12-31T20:00:00", "height": 30, "n_samples": 45256, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.137, 45.773 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway7_102", "wind_speed_mean": 9.4725552245517388, "time_start": "2006-05-18T20:00:00", "time_end": "2008-04-21T12:50:00", "height": 102, "n_samples": 101334, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.59, 39.431 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway7_82", "wind_speed_mean": 8.8151026820358496, "time_start": "2006-05-18T20:00:00", "time_end": "2008-04-21T12:50:00", "height": 82, "n_samples": 101334, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.59, 39.431 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway7_51", "wind_speed_mean": 8.0277816569163996, "time_start": "2006-05-18T20:00:00", "time_end": "2008-04-21T12:50:00", "height": 51, "n_samples": 101334, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.59, 39.431 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway7_120", "wind_speed_mean": 9.612451379032569, "time_start": "2006-05-18T20:00:00", "time_end": "2008-04-21T12:50:00", "height": 120, "n_samples": 101334, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.59, 39.431 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway7_122", "wind_speed_mean": 9.1932918059863837, "time_start": "2006-05-18T20:00:00", "time_end": "2008-04-21T12:50:00", "height": 122, "n_samples": 101334, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.59, 39.431 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway7_31", "wind_speed_mean": 7.0388759170894541, "time_start": "2006-05-18T20:00:00", "time_end": "2008-04-21T12:50:00", "height": 31, "n_samples": 101334, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.59, 39.431 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_fortgratiot_27", "wind_speed_mean": 4.5265887545769168, "time_start": "2006-09-15T21:00:00", "time_end": "2021-01-31T23:00:00", "height": 27, "n_samples": 122600, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.422, 43.007 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_muskegon_24", "wind_speed_mean": 5.8158032627291538, "time_start": "2005-02-14T20:00:00", "time_end": "2021-12-31T23:00:00", "height": 24, "n_samples": 140614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -86.339, 43.227 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_rincondelsanjose_10", "wind_speed_mean": 6.3983407444372169, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 116190, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.471, 26.801 ] } }, +{ "type": "Feature", "properties": { "site_id": "wa_naselleridge_30", "wind_speed_mean": 6.1848536150859372, "time_start": "2010-02-01T08:00:00", "time_end": "2018-05-01T06:00:00", "height": 30, "n_samples": 72255, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -123.796896, 46.421801 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_toledocrib_18", "wind_speed_mean": 6.6594923247641944, "time_start": "2019-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 18, "n_samples": 22065, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.259, 41.699 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_balko_40", "wind_speed_mean": 7.9054518211931661, "time_start": "2009-12-16T06:00:00", "time_end": "2012-10-30T05:50:00", "height": 40, "n_samples": 151056, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.678, 36.5709 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_balko_60", "wind_speed_mean": 8.6773165392516738, "time_start": "2009-12-16T06:00:00", "time_end": "2012-10-30T05:50:00", "height": 60, "n_samples": 151056, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.678, 36.5709 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_shellbeach_16", "wind_speed_mean": 4.9303011827825873, "time_start": "2009-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 16, "n_samples": 109620, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.673, 29.868 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_brookhaven_10", "wind_speed_mean": 2.1485702445089103, "time_start": "2007-01-01T05:00:00", "time_end": "2013-12-31T04:00:00", "height": 10, "n_samples": 59352, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.88884, 40.870731 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_brookhaven_50", "wind_speed_mean": 4.1302334365792861, "time_start": "2007-01-01T05:00:00", "time_end": "2013-12-31T04:00:00", "height": 50, "n_samples": 59352, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.88884, 40.870731 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_brookhaven_85", "wind_speed_mean": 5.7225448837783501, "time_start": "2007-01-01T05:00:00", "time_end": "2013-12-31T04:00:00", "height": 85, "n_samples": 59352, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.88884, 40.870731 ] } }, +{ "type": "Feature", "properties": { "site_id": "ar_lepanto_36", "wind_speed_mean": 5.3897208654506601, "time_start": "2011-03-28T18:50:00", "time_end": "2012-04-01T05:40:00", "height": 36, "n_samples": 53202, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.30374, 35.6116 ] } }, +{ "type": "Feature", "properties": { "site_id": "ar_lepanto_53", "wind_speed_mean": 6.0799337319571922, "time_start": "2011-03-28T18:50:00", "time_end": "2012-04-01T05:40:00", "height": 53, "n_samples": 53202, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.30374, 35.6116 ] } }, +{ "type": "Feature", "properties": { "site_id": "me_mountdesertrock_23", "wind_speed_mean": 8.3314002513479544, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 23, "n_samples": 150449, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -68.128, 43.969 ] } }, +{ "type": "Feature", "properties": { "site_id": "de_lewes_10", "wind_speed_mean": 4.8650687774644661, "time_start": "2005-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 145589, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -75.119, 38.783 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_keywest_15", "wind_speed_mean": 3.3354204529568015, "time_start": "2006-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 15, "n_samples": 136385, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -81.808, 24.556 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_dunkirk_20", "wind_speed_mean": 5.386944869831547, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 20, "n_samples": 191233, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -79.354, 42.494 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_100", "wind_speed_mean": 6.8343610679108346, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 100, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_36", "wind_speed_mean": 4.9992774205538009, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 36, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_70", "wind_speed_mean": 6.178539264505182, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 70, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_39", "wind_speed_mean": 5.126353680697199, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 39, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_80", "wind_speed_mean": 6.4286119402985076, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 80, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_144", "wind_speed_mean": 7.4931766903249892, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 144, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_50", "wind_speed_mean": 5.5460871153420408, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 50, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_124", "wind_speed_mean": 7.2262250046511625, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 124, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_56", "wind_speed_mean": 5.7588122812833822, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 56, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_90", "wind_speed_mean": 6.6431734659910493, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 90, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_leipsic_60", "wind_speed_mean": 5.8877854590991126, "time_start": "2017-06-30T21:20:00", "time_end": "2018-01-08T23:50:00", "height": 60, "n_samples": 27614, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.98729, 41.10925 ] } }, +{ "type": "Feature", "properties": { "site_id": "ri_providence_18", "wind_speed_mean": 3.6722078134831011, "time_start": "2005-01-01T00:18:00", "time_end": "2021-12-31T23:54:00", "height": 18, "n_samples": 1427173, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -71.401, 41.807 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_southtimbalierblock_10", "wind_speed_mean": 6.5122913417663035, "time_start": "2011-01-01T00:00:00", "time_end": "2020-04-13T17:00:00", "height": 10, "n_samples": 70017, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.483, 28.867 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_duluth_13", "wind_speed_mean": 3.5906251906068931, "time_start": "2005-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 13, "n_samples": 144990, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.092, 46.776 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain4_58", "wind_speed_mean": 8.4065874028867675, "time_start": "2011-03-31T23:00:00", "time_end": "2013-05-28T21:00:00", "height": 58, "n_samples": 113605, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.30272, 34.46899 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain4_53", "wind_speed_mean": 8.1329480402817662, "time_start": "2011-03-31T23:00:00", "time_end": "2013-05-28T21:00:00", "height": 53, "n_samples": 113605, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.30272, 34.46899 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain4_38", "wind_speed_mean": 7.6935399169094518, "time_start": "2011-03-31T23:00:00", "time_end": "2013-05-28T21:00:00", "height": 38, "n_samples": 113605, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.30272, 34.46899 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain4_23", "wind_speed_mean": 6.8363562236570381, "time_start": "2011-03-31T23:00:00", "time_end": "2013-05-28T21:00:00", "height": 23, "n_samples": 113605, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.30272, 34.46899 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_100", "wind_speed_mean": 8.074044401475275, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 100, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_36", "wind_speed_mean": 5.535813038902532, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 36, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_70", "wind_speed_mean": 7.1851312238742784, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 70, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_39", "wind_speed_mean": 5.7285095333943268, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 39, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_80", "wind_speed_mean": 7.5179429107051279, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 80, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_144", "wind_speed_mean": 8.9741945352381638, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 144, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_50", "wind_speed_mean": 6.3623867345304408, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 50, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_124", "wind_speed_mean": 8.6179684783594972, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 124, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_56", "wind_speed_mean": 6.6402675526180914, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 56, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_90", "wind_speed_mean": 7.8068303287003253, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 90, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "nd_dickinson_60", "wind_speed_mean": 6.8112312430505311, "time_start": "2020-11-13T17:20:00", "time_end": "2022-01-25T15:00:00", "height": 60, "n_samples": 61968, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -102.9111, 46.8535 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_100", "wind_speed_mean": 6.0201983555158751, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 100, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_36", "wind_speed_mean": 4.3988777007095594, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 36, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_70", "wind_speed_mean": 5.3338683822787356, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 70, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_39", "wind_speed_mean": 4.5084748533359855, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 39, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_80", "wind_speed_mean": 5.5545728303888362, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 80, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_144", "wind_speed_mean": 7.0056471094908064, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 144, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_50", "wind_speed_mean": 4.8445762179983722, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 50, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_124", "wind_speed_mean": 6.6147984436428482, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 124, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_56", "wind_speed_mean": 5.0001853312617035, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 56, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_90", "wind_speed_mean": 5.7809297011939869, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 90, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_marion_60", "wind_speed_mean": 5.101429521489055, "time_start": "2017-08-15T14:50:00", "time_end": "2018-08-16T15:20:00", "height": 60, "n_samples": 52652, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.17704, 40.59067 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_100", "wind_speed_mean": 6.9365525703405959, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 100, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_132", "wind_speed_mean": 7.45753317627734, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 132, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_70", "wind_speed_mean": 6.2968977067874059, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 70, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_39", "wind_speed_mean": 5.3249088334513353, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 39, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_40", "wind_speed_mean": 5.3639985503288017, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 40, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_60", "wind_speed_mean": 6.027633857856352, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 60, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_80", "wind_speed_mean": 6.5322502259685118, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 80, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_144", "wind_speed_mean": 7.6211214298153962, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 144, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_50", "wind_speed_mean": 5.7228497985764664, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 50, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_90", "wind_speed_mean": 6.7495798158177562, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 90, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_woodville_28", "wind_speed_mean": 4.8525541665797105, "time_start": "2021-05-06T17:30:00", "time_end": "2022-05-03T14:10:00", "height": 28, "n_samples": 48622, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.35856, 41.46547 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_santabarbara_13", "wind_speed_mean": 2.5097666068222622, "time_start": "2014-04-29T14:00:00", "time_end": "2021-12-31T23:00:00", "height": 13, "n_samples": 66349, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -119.692, 34.405 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_marshisland_20", "wind_speed_mean": 5.1325284395793096, "time_start": "2011-01-01T00:00:00", "time_end": "2020-04-13T17:00:00", "height": 20, "n_samples": 66430, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.061, 29.441 ] } }, +{ "type": "Feature", "properties": { "site_id": "va_yorktown_10", "wind_speed_mean": 4.3412428147424578, "time_start": "2006-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 136697, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -76.479, 37.227 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_northjettylanding_14", "wind_speed_mean": 3.9532067593136295, "time_start": "2020-03-02T23:00:00", "time_end": "2021-12-31T23:00:00", "height": 14, "n_samples": 15598, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -124.236, 40.769 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar3_10", "wind_speed_mean": 5.2508062572621261, "time_start": "2001-08-18T00:00:00", "time_end": "2007-04-16T21:00:00", "height": 10, "n_samples": 47439, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.685, 43.66 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar3_49", "wind_speed_mean": 7.2878864628820956, "time_start": "2001-08-18T00:00:00", "time_end": "2007-04-16T21:00:00", "height": 49, "n_samples": 47439, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.685, 43.66 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar3_30", "wind_speed_mean": 6.5630063134305843, "time_start": "2001-08-18T00:00:00", "time_end": "2007-04-16T21:00:00", "height": 30, "n_samples": 47439, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.685, 43.66 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_sarita_51", "wind_speed_mean": 6.5808354491841756, "time_start": "2009-06-04T23:00:00", "time_end": "2014-01-01T05:50:00", "height": 51, "n_samples": 198683, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.5799, 27.2019 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_sarita_60", "wind_speed_mean": 6.8789609838417931, "time_start": "2009-06-04T23:00:00", "time_end": "2014-01-01T05:50:00", "height": 60, "n_samples": 198683, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.5799, 27.2019 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_sarita_31", "wind_speed_mean": 5.6626667810500395, "time_start": "2009-06-04T23:00:00", "time_end": "2014-01-01T05:50:00", "height": 31, "n_samples": 198683, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.5799, 27.2019 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail3_59", "wind_speed_mean": 7.4617282593603926, "time_start": "2010-03-18T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 59, "n_samples": 86794, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.021, 40.489 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail3_20", "wind_speed_mean": 6.0504265913876489, "time_start": "2010-03-18T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 20, "n_samples": 86794, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.021, 40.489 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail3_39", "wind_speed_mean": 6.8866454542295514, "time_start": "2010-03-18T00:10:00", "time_end": "2012-01-01T05:50:00", "height": 39, "n_samples": 86794, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.021, 40.489 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_portsanilac_10", "wind_speed_mean": 4.4299173359144062, "time_start": "2012-03-19T21:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 81873, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.54, 43.42 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_cedarkey_10", "wind_speed_mean": 3.94074511285296, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 191284, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.029, 29.136 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion_40", "wind_speed_mean": 6.8524983992699653, "time_start": "2006-05-17T15:20:00", "time_end": "2010-04-12T20:00:00", "height": 40, "n_samples": 205371, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.694, 32.515 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion_20", "wind_speed_mean": 6.0778643108413801, "time_start": "2006-05-17T15:20:00", "time_end": "2010-04-12T20:00:00", "height": 20, "n_samples": 205371, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.694, 32.515 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion_60", "wind_speed_mean": 7.6573888020018748, "time_start": "2006-05-17T15:20:00", "time_end": "2010-04-12T20:00:00", "height": 60, "n_samples": 205371, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.694, 32.515 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_angelsgate_20", "wind_speed_mean": 3.5618234780075739, "time_start": "2017-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 20, "n_samples": 35125, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.246, 33.716 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_100", "wind_speed_mean": 5.8481266650436474, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 100, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_36", "wind_speed_mean": 4.3857788117877128, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 36, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_70", "wind_speed_mean": 5.1777715474606909, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 70, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_39", "wind_speed_mean": 4.462522989860882, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 39, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_80", "wind_speed_mean": 5.3997778197554611, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 80, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_144", "wind_speed_mean": 6.7889458743777151, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 144, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_50", "wind_speed_mean": 4.7228486185983822, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 50, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_124", "wind_speed_mean": 6.3898367081487732, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 124, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_56", "wind_speed_mean": 4.859740113567228, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 56, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_90", "wind_speed_mean": 5.6238721175453756, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 90, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_newport_60", "wind_speed_mean": 4.95287186440678, "time_start": "2018-07-26T17:10:00", "time_end": "2019-03-19T15:40:00", "height": 60, "n_samples": 33800, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.42033, 39.90949 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_martinez_100", "wind_speed_mean": 4.8666891574147906, "time_start": "2014-01-01T00:00:00", "time_end": "2021-08-02T02:00:00", "height": 100, "n_samples": 49409, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -122.121, 38.038 ] } }, +{ "type": "Feature", "properties": { "site_id": "al_fortmorgan_36", "wind_speed_mean": 6.2516353241488751, "time_start": "2009-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 36, "n_samples": 107580, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.024, 30.228 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_eastpoint_35", "wind_speed_mean": 6.1970394760752177, "time_start": "2004-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 35, "n_samples": 138692, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.858, 29.408 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_mackinawcity_10", "wind_speed_mean": 4.5337652995055526, "time_start": "2010-01-01T01:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 101470, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.721, 45.777 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_ameradapass_11", "wind_speed_mean": 2.3469310621820356, "time_start": "2009-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 108546, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -91.338, 29.45 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_pilottown_10", "wind_speed_mean": 3.6260847469932003, "time_start": "2012-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 85829, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.259, 29.179 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_detourvillage_12", "wind_speed_mean": 3.8168461350959944, "time_start": "2005-01-01T04:00:00", "time_end": "2021-12-31T23:00:00", "height": 12, "n_samples": 142379, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.898, 45.993 ] } }, +{ "type": "Feature", "properties": { "site_id": "wi_deathsdoor_10", "wind_speed_mean": 4.1539447974648214, "time_start": "2012-01-01T01:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 77011, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -86.977, 45.291 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_lajolla_20", "wind_speed_mean": 2.5242658638258453, "time_start": "2015-08-21T22:00:00", "time_end": "2021-12-31T23:00:00", "height": 20, "n_samples": 53758, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -117.257, 32.867 ] } }, +{ "type": "Feature", "properties": { "site_id": "co_nwtc_80", "wind_speed_mean": 4.7797588990910498, "time_start": "2002-01-01T07:00:00", "time_end": "2022-01-01T06:00:00", "height": 80, "n_samples": 175318, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -105.2347, 39.9106 ] } }, +{ "type": "Feature", "properties": { "site_id": "co_nwtc_50", "wind_speed_mean": 4.5753861984208282, "time_start": "2002-01-01T07:00:00", "time_end": "2022-01-01T06:00:00", "height": 50, "n_samples": 175318, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -105.2347, 39.9106 ] } }, +{ "type": "Feature", "properties": { "site_id": "co_nwtc_10", "wind_speed_mean": 3.6636513679799605, "time_start": "2002-01-01T07:00:00", "time_end": "2022-01-01T06:00:00", "height": 10, "n_samples": 175318, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -105.2347, 39.9106 ] } }, +{ "type": "Feature", "properties": { "site_id": "co_nwtc_20", "wind_speed_mean": 4.0233171942770305, "time_start": "2002-01-01T07:00:00", "time_end": "2022-01-01T06:00:00", "height": 20, "n_samples": 175318, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -105.2347, 39.9106 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_moda_17", "wind_speed_mean": 4.290085071143567, "time_start": "2020-05-26T14:00:00", "time_end": "2021-12-31T23:00:00", "height": 17, "n_samples": 13613, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.201, 27.828 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_losangelesbadger_62", "wind_speed_mean": 3.0260697970726249, "time_start": "2017-01-01T00:00:00", "time_end": "2021-12-31T22:00:00", "height": 62, "n_samples": 32655, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.24, 33.766 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_calcasieupass_12", "wind_speed_mean": 5.0001884539465671, "time_start": "2007-01-30T19:00:00", "time_end": "2021-12-31T23:00:00", "height": 12, "n_samples": 125643, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -93.343, 29.768 ] } }, +{ "type": "Feature", "properties": { "site_id": "sc_follyisland_10", "wind_speed_mean": 4.7365716091318424, "time_start": "2000-01-12T15:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 190379, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -79.888, 32.685 ] } }, +{ "type": "Feature", "properties": { "site_id": "vt_proctormaple_16", "wind_speed_mean": 1.6349843830646944, "time_start": "2007-01-01T05:00:00", "time_end": "2014-01-01T04:45:00", "height": 16, "n_samples": 217536, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.866062, 44.52647 ] } }, +{ "type": "Feature", "properties": { "site_id": "vt_proctormaple_24", "wind_speed_mean": 2.311411327788659, "time_start": "2007-01-01T05:00:00", "time_end": "2014-01-01T04:45:00", "height": 24, "n_samples": 217536, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -72.866062, 44.52647 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion5_40", "wind_speed_mean": 7.0918516011957191, "time_start": "2007-01-10T21:20:00", "time_end": "2010-05-22T17:50:00", "height": 40, "n_samples": 176296, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.61, 32.414 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion5_58", "wind_speed_mean": 7.5163930742186951, "time_start": "2007-01-10T21:20:00", "time_end": "2010-05-22T17:50:00", "height": 58, "n_samples": 176296, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.61, 32.414 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion5_20", "wind_speed_mean": 6.9520385841516337, "time_start": "2007-01-10T21:20:00", "time_end": "2010-05-22T17:50:00", "height": 20, "n_samples": 176296, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.61, 32.414 ] } }, +{ "type": "Feature", "properties": { "site_id": "mo_ozark_30", "wind_speed_mean": 2.720358949650687, "time_start": "2007-01-01T00:00:00", "time_end": "2008-01-01T05:30:00", "height": 30, "n_samples": 16647, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.2, 40.033 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_berwick_13", "wind_speed_mean": 2.6026327419660333, "time_start": "2012-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 13, "n_samples": 84404, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -91.237, 29.668 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_grandtraversebay_10", "wind_speed_mean": 3.3384468302842065, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 120116, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.241, 47.179 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_clambayou_11", "wind_speed_mean": 2.2503853030022811, "time_start": "2018-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 34650, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.686, 27.736 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_baffinbay_10", "wind_speed_mean": 5.8708349218452156, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 118188, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.405, 27.297 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar4_10", "wind_speed_mean": 5.4386826705985349, "time_start": "2001-11-25T16:00:00", "time_end": "2007-04-17T18:00:00", "height": 10, "n_samples": 44527, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.61, 43.688 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar4_49", "wind_speed_mean": 7.5995855722884604, "time_start": "2001-11-25T16:00:00", "time_end": "2007-04-17T18:00:00", "height": 49, "n_samples": 44527, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.61, 43.688 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar4_30", "wind_speed_mean": 6.7549865538878802, "time_start": "2001-11-25T16:00:00", "time_end": "2007-04-17T18:00:00", "height": 30, "n_samples": 44527, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.61, 43.688 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_bigbay_10", "wind_speed_mean": 2.7883273299267488, "time_start": "2007-06-01T16:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 121641, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.727, 46.827 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_whitefishpoint_17", "wind_speed_mean": 4.252520912893254, "time_start": "2016-09-16T18:00:00", "time_end": "2021-12-31T23:00:00", "height": 17, "n_samples": 45018, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.97, 46.76 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_doepke_32", "wind_speed_mean": 5.3086509981920988, "time_start": "2011-01-18T06:00:00", "time_end": "2012-01-20T12:50:00", "height": 32, "n_samples": 52890, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -94.79589, 39.0378 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_newcanalstation_10", "wind_speed_mean": 3.2740956916494852, "time_start": "2009-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 109334, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.113, 30.027 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail2_40", "wind_speed_mean": 6.4561980214919172, "time_start": "2008-10-02T06:00:00", "time_end": "2011-12-20T02:50:00", "height": 40, "n_samples": 169032, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.976, 40.456 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail2_59", "wind_speed_mean": 6.8666652618650561, "time_start": "2008-10-02T06:00:00", "time_end": "2011-12-20T02:50:00", "height": 59, "n_samples": 169032, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.976, 40.456 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail2_20", "wind_speed_mean": 5.2881050032079608, "time_start": "2008-10-02T06:00:00", "time_end": "2011-12-20T02:50:00", "height": 20, "n_samples": 169032, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.976, 40.456 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_pilotsstation_20", "wind_speed_mean": 5.3903929917768618, "time_start": "2007-06-14T01:00:00", "time_end": "2021-12-31T23:00:00", "height": 20, "n_samples": 119900, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.407, 28.932 ] } }, +{ "type": "Feature", "properties": { "site_id": "al_perdidopass_12", "wind_speed_mean": 3.4184623143223916, "time_start": "2012-01-01T00:00:00", "time_end": "2017-12-30T22:00:00", "height": 12, "n_samples": 50045, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.556, 30.279 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_losangelespierf_23", "wind_speed_mean": 2.9925711553049044, "time_start": "2014-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 23, "n_samples": 62777, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.215, 33.747 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_santamonica_14", "wind_speed_mean": 2.9091366459627332, "time_start": "2010-01-22T18:00:00", "time_end": "2021-12-31T23:00:00", "height": 14, "n_samples": 102852, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.5, 34.008 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar_100", "wind_speed_mean": 8.0740995089053342, "time_start": "2007-01-01T06:00:00", "time_end": "2011-09-07T05:50:00", "height": 100, "n_samples": 228545, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.698, 43.673 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar_10", "wind_speed_mean": 4.9838035586167315, "time_start": "2007-01-01T06:00:00", "time_end": "2011-09-07T05:50:00", "height": 10, "n_samples": 228545, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.698, 43.673 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar_80", "wind_speed_mean": 7.3375205297824415, "time_start": "2007-01-01T06:00:00", "time_end": "2011-09-07T05:50:00", "height": 80, "n_samples": 228545, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.698, 43.673 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar_50", "wind_speed_mean": 6.6632402086673572, "time_start": "2007-01-01T06:00:00", "time_end": "2011-09-07T05:50:00", "height": 50, "n_samples": 228545, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.698, 43.673 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_prairiestar_30", "wind_speed_mean": 6.2590814406000153, "time_start": "2007-01-01T06:00:00", "time_end": "2011-09-07T05:50:00", "height": 30, "n_samples": 228545, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.698, 43.673 ] } }, +{ "type": "Feature", "properties": { "site_id": "sc_spiderweb_34", "wind_speed_mean": 2.9592680634538722, "time_start": "2009-01-01T00:15:00", "time_end": "2014-01-01T00:00:00", "height": 34, "n_samples": 175296, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -81.83, 33.41 ] } }, +{ "type": "Feature", "properties": { "site_id": "sc_spiderweb_68", "wind_speed_mean": 4.2217108995223969, "time_start": "2009-01-01T00:15:00", "time_end": "2014-01-01T00:00:00", "height": 68, "n_samples": 175296, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -81.83, 33.41 ] } }, +{ "type": "Feature", "properties": { "site_id": "mn_silverbay_10", "wind_speed_mean": 4.1874701670644399, "time_start": "2006-07-04T00:00:00", "time_end": "2021-12-31T22:00:00", "height": 10, "n_samples": 106502, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -91.27, 47.278 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_argonne_10", "wind_speed_mean": 3.3652911433697597, "time_start": "2007-01-01T07:00:00", "time_end": "2021-01-01T06:00:00", "height": 10, "n_samples": 122629, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.99495, 41.70121 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_argonne_60", "wind_speed_mean": 5.3676158487389074, "time_start": "2007-01-01T07:00:00", "time_end": "2021-01-01T06:00:00", "height": 60, "n_samples": 122629, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.99495, 41.70121 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain2_48", "wind_speed_mean": 7.3474450845302428, "time_start": "2011-02-11T16:40:00", "time_end": "2013-09-30T16:00:00", "height": 48, "n_samples": 138525, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.24472, 34.389394 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain2_33", "wind_speed_mean": 6.7974408041789918, "time_start": "2011-02-11T16:40:00", "time_end": "2013-09-30T16:00:00", "height": 33, "n_samples": 138525, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.24472, 34.389394 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain2_18", "wind_speed_mean": 6.1412946813562233, "time_start": "2011-02-11T16:40:00", "time_end": "2013-09-30T16:00:00", "height": 18, "n_samples": 138525, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.24472, 34.389394 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_arbucklemountain2_53", "wind_speed_mean": 7.4749866423598279, "time_start": "2011-02-11T16:40:00", "time_end": "2013-09-30T16:00:00", "height": 53, "n_samples": 138525, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.24472, 34.389394 ] } }, +{ "type": "Feature", "properties": { "site_id": "va_capehenry_28", "wind_speed_mean": 5.409826707774843, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 28, "n_samples": 126658, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -76.007, 36.926 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_wasco_30", "wind_speed_mean": 5.798209883037905, "time_start": "2005-09-03T23:40:00", "time_end": "2018-05-01T14:50:00", "height": 30, "n_samples": 660788, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -120.7669, 45.500263 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_losangelespier400_11", "wind_speed_mean": 2.7161000740375121, "time_start": "2017-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 33448, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.241, 33.735 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion6_40", "wind_speed_mean": 7.0867893714112107, "time_start": "2006-12-18T00:50:00", "time_end": "2022-05-09T13:15:00", "height": 40, "n_samples": 68502, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.572, 32.412 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion6_58", "wind_speed_mean": 7.612891449311106, "time_start": "2006-12-18T00:50:00", "time_end": "2022-05-09T13:15:00", "height": 58, "n_samples": 68502, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.572, 32.412 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion6_20", "wind_speed_mean": 6.1970978491785376, "time_start": "2006-12-18T00:50:00", "time_end": "2022-05-09T13:15:00", "height": 20, "n_samples": 68502, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.572, 32.412 ] } }, +{ "type": "Feature", "properties": { "site_id": "va_rappahannock_17", "wind_speed_mean": 6.4480613434805294, "time_start": "2006-03-01T01:00:00", "time_end": "2021-12-31T23:00:00", "height": 17, "n_samples": 135872, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -76.014, 37.538 ] } }, +{ "type": "Feature", "properties": { "site_id": "nc_capelookout_10", "wind_speed_mean": 5.7169576109697102, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 150889, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -76.525, 34.622 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_thunderbayisland_11", "wind_speed_mean": 4.8639666371420134, "time_start": "2013-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 68114, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.194, 45.035 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_foweyrock_44", "wind_speed_mean": 6.7010464969830208, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 44, "n_samples": 175455, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -80.097, 25.591 ] } }, +{ "type": "Feature", "properties": { "site_id": "la_freshwatercanal_17", "wind_speed_mean": 3.9788475099162621, "time_start": "2009-06-01T00:00:00", "time_end": "2021-10-16T04:00:00", "height": 17, "n_samples": 93848, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -92.305, 29.552 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_isleroyale_47", "wind_speed_mean": 7.627308217729535, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 47, "n_samples": 177767, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -89.31, 47.87 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_lenoxwarren_49", "wind_speed_mean": 6.4682931426354022, "time_start": "2007-10-20T21:10:00", "time_end": "2010-06-30T19:50:00", "height": 49, "n_samples": 141689, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.66613, 40.8336 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_lenoxwarren_30", "wind_speed_mean": 5.9386951556941456, "time_start": "2007-10-20T21:10:00", "time_end": "2010-06-30T19:50:00", "height": 30, "n_samples": 141689, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.66613, 40.8336 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_northspit_14", "wind_speed_mean": 4.0197970256176143, "time_start": "2009-02-25T07:00:00", "time_end": "2019-03-22T05:00:00", "height": 14, "n_samples": 87302, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -124.217, 40.767 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_molassesreef_16", "wind_speed_mean": 6.0448511083116658, "time_start": "2000-01-01T00:00:00", "time_end": "2019-03-31T23:00:00", "height": 16, "n_samples": 152339, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -80.376, 25.012 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_barcelona_10", "wind_speed_mean": 5.3803179091360658, "time_start": "2011-01-14T19:00:00", "time_end": "2014-09-03T23:00:00", "height": 10, "n_samples": 26357, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -79.595, 42.345 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_olcottharbor_10", "wind_speed_mean": 4.7042923992177021, "time_start": "2008-01-01T01:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 100737, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -78.719, 43.341 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_marinersharbor_46", "wind_speed_mean": 4.5123547982353189, "time_start": "2016-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 46, "n_samples": 49646, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -74.162, 40.641 ] } }, +{ "type": "Feature", "properties": { "site_id": "wi_portwashington_10", "wind_speed_mean": 3.3858244913034725, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 120993, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.867, 43.388 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_keatonbeach_10", "wind_speed_mean": 3.8531392721123905, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 190593, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.594, 29.819 ] } }, +{ "type": "Feature", "properties": { "site_id": "wi_sheboygan_19", "wind_speed_mean": 5.9209806670474405, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 19, "n_samples": 174949, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.693, 43.749 ] } }, +{ "type": "Feature", "properties": { "site_id": "wi_portwing_10", "wind_speed_mean": 4.0367703405612518, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 94474, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -91.386, 46.792 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_naubinway_10", "wind_speed_mean": 4.2183845675171128, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 124911, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -85.444, 46.087 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_fairport_21", "wind_speed_mean": 6.3331103393000276, "time_start": "2010-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 21, "n_samples": 102075, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -81.281, 41.764 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_saginawbay_13", "wind_speed_mean": 6.6649913096625095, "time_start": "2009-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 13, "n_samples": 109965, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -83.72, 43.81 ] } }, +{ "type": "Feature", "properties": { "site_id": "de_brandywineshoal_21", "wind_speed_mean": 6.765486668041282, "time_start": "2006-03-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 21, "n_samples": 105966, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -75.113, 38.987 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion2_40", "wind_speed_mean": 6.6795803191746241, "time_start": "2007-03-07T18:00:00", "time_end": "2008-03-27T16:00:00", "height": 40, "n_samples": 55537, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.595, 32.392 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion2_58", "wind_speed_mean": 7.1684295049425053, "time_start": "2007-03-07T18:00:00", "time_end": "2008-03-27T16:00:00", "height": 58, "n_samples": 55537, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.595, 32.392 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_champion2_20", "wind_speed_mean": 5.8149005351036012, "time_start": "2007-03-07T18:00:00", "time_end": "2008-03-27T16:00:00", "height": 20, "n_samples": 55537, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -100.595, 32.392 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_northerlyisle_10", "wind_speed_mean": 4.0113484403413064, "time_start": "2013-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 74791, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -87.609, 41.856 ] } }, +{ "type": "Feature", "properties": { "site_id": "in_michigancity_21", "wind_speed_mean": 6.0913405656722208, "time_start": "2006-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 21, "n_samples": 127582, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -86.912, 41.729 ] } }, +{ "type": "Feature", "properties": { "site_id": "ma_bordenflats_16", "wind_speed_mean": 4.7527339012522463, "time_start": "2005-01-01T02:00:00", "time_end": "2021-12-31T23:00:00", "height": 16, "n_samples": 141016, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -71.174, 41.704 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_huronharbor_10", "wind_speed_mean": 4.6904769611576533, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 106669, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.545, 41.401 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_100", "wind_speed_mean": 6.9570036847689778, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 100, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_36", "wind_speed_mean": 5.1317693751099469, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 36, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_70", "wind_speed_mean": 6.2578187733415467, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 70, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_39", "wind_speed_mean": 5.2586944514870799, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 39, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_80", "wind_speed_mean": 6.510073560295166, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 80, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_144", "wind_speed_mean": 7.7745182242990643, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 144, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_50", "wind_speed_mean": 5.6674856651265308, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 50, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_124", "wind_speed_mean": 7.4321773286262944, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 124, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_56", "wind_speed_mean": 5.8581830002314987, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 56, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_90", "wind_speed_mean": 6.7423384888340339, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 90, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "oh_greenville_60", "wind_speed_mean": 5.9772792289156627, "time_start": "2017-01-27T17:10:00", "time_end": "2018-02-01T23:50:00", "height": 60, "n_samples": 53274, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.60851, 40.132 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_rochester_10", "wind_speed_mean": 4.8686923499353103, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 111784, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -77.598, 43.263 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_portaransas_11", "wind_speed_mean": 4.3004611278122136, "time_start": "2008-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 115791, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.072, 27.84 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail4_59", "wind_speed_mean": 7.4607668697708274, "time_start": "2010-03-15T00:10:00", "time_end": "2011-03-25T00:00:00", "height": 59, "n_samples": 52166, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.059, 40.463 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail4_22", "wind_speed_mean": 6.0186400218127281, "time_start": "2010-03-15T00:10:00", "time_end": "2011-03-25T00:00:00", "height": 22, "n_samples": 52166, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.059, 40.463 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail4_39", "wind_speed_mean": 6.8466301818181821, "time_start": "2010-03-15T00:10:00", "time_end": "2011-03-25T00:00:00", "height": 39, "n_samples": 52166, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.059, 40.463 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_panhandle_10", "wind_speed_mean": 6.4284309871242753, "time_start": "2007-07-25T20:20:00", "time_end": "2013-09-01T05:50:00", "height": 10, "n_samples": 317662, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -101.345, 35.414 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_panhandle_50", "wind_speed_mean": 8.5749374711214958, "time_start": "2007-07-25T20:20:00", "time_end": "2013-09-01T05:50:00", "height": 50, "n_samples": 317662, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -101.345, 35.414 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_panhandle_58", "wind_speed_mean": 8.8942000495426345, "time_start": "2007-07-25T20:20:00", "time_end": "2013-09-01T05:50:00", "height": 58, "n_samples": 317662, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -101.345, 35.414 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_panhandle_30", "wind_speed_mean": 7.7313103165052732, "time_start": "2007-07-25T20:20:00", "time_end": "2013-09-01T05:50:00", "height": 30, "n_samples": 317662, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -101.345, 35.414 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_littlerapids_10", "wind_speed_mean": 3.3461383048620235, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 127431, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -84.302, 46.486 ] } }, +{ "type": "Feature", "properties": { "site_id": "ca_losangelesberth161_27", "wind_speed_mean": 2.3518602669696107, "time_start": "2017-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 27, "n_samples": 29323, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.265, 33.764 ] } }, +{ "type": "Feature", "properties": { "site_id": "fl_venice_12", "wind_speed_mean": 4.2504083702866415, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 12, "n_samples": 173085, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -82.453, 27.072 ] } }, +{ "type": "Feature", "properties": { "site_id": "wi_devilsisland_25", "wind_speed_mean": 6.0547873701448234, "time_start": "2000-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 25, "n_samples": 176656, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -90.728, 47.079 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_galvestonbridge_11", "wind_speed_mean": 4.7091057312252964, "time_start": "2017-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 11, "n_samples": 42953, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -94.896, 29.302 ] } }, +{ "type": "Feature", "properties": { "site_id": "nj_robbinsreef_21", "wind_speed_mean": 5.728143748194225, "time_start": "2007-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 21, "n_samples": 121419, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -74.065, 40.657 ] } }, +{ "type": "Feature", "properties": { "site_id": "mi_fairport_10", "wind_speed_mean": 4.8299619117622488, "time_start": "2006-12-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 10, "n_samples": 125585, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -86.66, 45.619 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_butlergrade_45", "wind_speed_mean": 7.0954614740856776, "time_start": "2012-01-01T00:00:00", "time_end": "2018-05-01T06:50:00", "height": 45, "n_samples": 332934, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.6834, 45.95008 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_butlergrade_62", "wind_speed_mean": 7.2824994517125905, "time_start": "2012-01-01T00:00:00", "time_end": "2018-05-01T06:50:00", "height": 62, "n_samples": 332934, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.6834, 45.95008 ] } }, +{ "type": "Feature", "properties": { "site_id": "or_butlergrade_31", "wind_speed_mean": 6.8784000038663393, "time_start": "2012-01-01T00:00:00", "time_end": "2018-05-01T06:50:00", "height": 31, "n_samples": 332934, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -118.6834, 45.95008 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway_49", "wind_speed_mean": 7.5339314695773947, "time_start": "2006-07-03T06:00:00", "time_end": "2008-12-23T05:50:00", "height": 49, "n_samples": 130176, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.613, 39.428 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway_10", "wind_speed_mean": 5.3613871983876971, "time_start": "2006-07-03T06:00:00", "time_end": "2008-12-23T05:50:00", "height": 10, "n_samples": 130176, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.613, 39.428 ] } }, +{ "type": "Feature", "properties": { "site_id": "ks_meridianway_30", "wind_speed_mean": 6.897553750218175, "time_start": "2006-07-03T06:00:00", "time_end": "2008-12-23T05:50:00", "height": 30, "n_samples": 130176, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.613, 39.428 ] } }, +{ "type": "Feature", "properties": { "site_id": "ny_oswego_15", "wind_speed_mean": 5.1379582952233074, "time_start": "2005-01-01T04:00:00", "time_end": "2021-12-31T23:00:00", "height": 15, "n_samples": 144758, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -76.511, 43.464 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail_48", "wind_speed_mean": 6.9348473929752794, "time_start": "2007-08-17T19:00:00", "time_end": "2011-12-20T08:50:00", "height": 48, "n_samples": 225672, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.05, 40.487 ] } }, +{ "type": "Feature", "properties": { "site_id": "il_pioneertrail_80", "wind_speed_mean": 7.8186833851612381, "time_start": "2007-08-17T19:00:00", "time_end": "2011-12-20T08:50:00", "height": 80, "n_samples": 225672, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -88.05, 40.487 ] } }, +{ "type": "Feature", "properties": { "site_id": "tx_freeport_15", "wind_speed_mean": 5.5070058966640545, "time_start": "2019-01-01T00:00:00", "time_end": "2021-12-31T23:00:00", "height": 15, "n_samples": 25483, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -95.294, 28.936 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_sgp_65", "wind_speed_mean": 7.1834885252612786, "time_start": "2016-01-01T00:00:00", "time_end": "2020-01-14T22:45:00", "height": 65, "n_samples": 128480, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.48818, 36.607481 ] } }, +{ "type": "Feature", "properties": { "site_id": "ok_sgp_91", "wind_speed_mean": 7.7485339785672878, "time_start": "2016-01-01T00:00:00", "time_end": "2020-01-14T22:45:00", "height": 91, "n_samples": 128480, "type": "met_tower" }, "geometry": { "type": "Point", "coordinates": [ -97.48818, 36.607481 ] } } +] +} diff --git a/obs/vendor_obs_summary.geojson b/obs/vendor_obs_summary.geojson new file mode 100644 index 0000000..e8d58f5 --- /dev/null +++ b/obs/vendor_obs_summary.geojson @@ -0,0 +1,141 @@ +{ +"type": "FeatureCollection", +"name": "vendor_obs_summary", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "site_id": 3, "wind_speed_mean": 4.6301955980117917, "time_start": "2010-10-26T19:34:48", "time_end": "2023-04-24T02:50:00", "height": 37.0, "n_samples": 484054, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.310785, 41.651289 ] } }, +{ "type": "Feature", "properties": { "site_id": 5, "wind_speed_mean": 4.0336642238995646, "time_start": "2010-10-14T19:31:34", "time_end": "2023-04-24T02:50:39", "height": 37.0, "n_samples": 546102, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -71.451457, 41.732752 ] } }, +{ "type": "Feature", "properties": { "site_id": 6, "wind_speed_mean": 4.4576427503960554, "time_start": "2010-10-27T19:20:27", "time_end": "2023-04-12T17:51:48", "height": 37.0, "n_samples": 601557, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -81.692289, 36.213405 ] } }, +{ "type": "Feature", "properties": { "site_id": 7, "wind_speed_mean": 3.6714336997898918, "time_start": "2010-09-15T18:45:12", "time_end": "2023-04-17T13:40:02", "height": 37.0, "n_samples": 548769, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -72.00394, 42.110866 ] } }, +{ "type": "Feature", "properties": { "site_id": 8, "wind_speed_mean": 4.1903972368395381, "time_start": "2010-10-25T20:15:57", "time_end": "2023-04-24T02:51:37", "height": 37.0, "n_samples": 614948, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -71.099095, 42.408993 ] } }, +{ "type": "Feature", "properties": { "site_id": 9, "wind_speed_mean": 5.1064807238075351, "time_start": "2010-10-29T04:08:14", "time_end": "2022-08-12T09:10:02", "height": 37.0, "n_samples": 516933, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.790434, 40.689945 ] } }, +{ "type": "Feature", "properties": { "site_id": 10, "wind_speed_mean": 5.3556226091401768, "time_start": "2010-10-29T04:08:14", "time_end": "2022-08-12T09:10:02", "height": 37.0, "n_samples": 461654, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.788792, 40.689909 ] } }, +{ "type": "Feature", "properties": { "site_id": 11, "wind_speed_mean": 5.3101689130098864, "time_start": "2010-10-27T17:56:47", "time_end": "2023-04-24T02:50:46", "height": 37.0, "n_samples": 588883, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -88.029789, 39.53132 ] } }, +{ "type": "Feature", "properties": { "site_id": 12, "wind_speed_mean": 4.6947877389552541, "time_start": "2010-10-26T15:16:40", "time_end": "2023-04-24T03:10:09", "height": 37.0, "n_samples": 410014, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -63.28222, 44.742336 ] } }, +{ "type": "Feature", "properties": { "site_id": 13, "wind_speed_mean": 5.4596993747493316, "time_start": "2010-10-13T17:20:17", "time_end": "2023-04-24T02:50:42", "height": 37.0, "n_samples": 625829, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.643189, 41.549011 ] } }, +{ "type": "Feature", "properties": { "site_id": 17, "wind_speed_mean": 5.4234325660686329, "time_start": "2010-10-27T15:12:43", "time_end": "2023-04-24T02:50:02", "height": 37.0, "n_samples": 536147, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -63.640769, 46.430253 ] } }, +{ "type": "Feature", "properties": { "site_id": 19, "wind_speed_mean": 7.6987559813525559, "time_start": "2010-10-20T17:17:38", "time_end": "2020-11-01T06:29:00", "height": 37.0, "n_samples": 389544, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.5868, 61.53757 ] } }, +{ "type": "Feature", "properties": { "site_id": 20, "wind_speed_mean": 7.9147752994327512, "time_start": "2010-10-20T17:17:38", "time_end": "2021-11-01T05:01:26", "height": 32.0, "n_samples": 436492, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.58512, 61.53569 ] } }, +{ "type": "Feature", "properties": { "site_id": 21, "wind_speed_mean": 7.8707329152853802, "time_start": "2010-10-20T17:17:38", "time_end": "2023-04-24T02:50:54", "height": 37.0, "n_samples": 500740, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.58358, 61.53383 ] } }, +{ "type": "Feature", "properties": { "site_id": 22, "wind_speed_mean": 7.6757707747753576, "time_start": "2010-10-20T17:17:38", "time_end": "2023-04-24T02:50:54", "height": 32.0, "n_samples": 433687, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.58218, 61.53215 ] } }, +{ "type": "Feature", "properties": { "site_id": 23, "wind_speed_mean": 6.5960690378758082, "time_start": "2010-11-16T17:39:15", "time_end": "2023-04-24T03:00:02", "height": 37.0, "n_samples": 353603, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -161.90414, 59.757352 ] } }, +{ "type": "Feature", "properties": { "site_id": 24, "wind_speed_mean": 6.7268264005258613, "time_start": "2010-11-10T23:27:24", "time_end": "2023-04-24T02:50:02", "height": 37.0, "n_samples": 432052, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -161.90414, 59.757352 ] } }, +{ "type": "Feature", "properties": { "site_id": 25, "wind_speed_mean": 6.7327819018765327, "time_start": "2010-11-10T23:27:24", "time_end": "2023-04-24T03:00:02", "height": 37.0, "n_samples": 391466, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -161.90414, 59.757352 ] } }, +{ "type": "Feature", "properties": { "site_id": 26, "wind_speed_mean": 6.4630454804792583, "time_start": "2010-11-05T14:05:23", "time_end": "2023-04-24T03:00:00", "height": 37.0, "n_samples": 490760, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -160.76863, 63.909019 ] } }, +{ "type": "Feature", "properties": { "site_id": 27, "wind_speed_mean": 6.5911489297592958, "time_start": "2010-11-04T12:40:16", "time_end": "2023-04-24T03:00:00", "height": 37.0, "n_samples": 517734, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -160.76878, 63.909293 ] } }, +{ "type": "Feature", "properties": { "site_id": 28, "wind_speed_mean": 4.1404182133915013, "time_start": "2010-10-25T15:31:35", "time_end": "2023-04-24T02:50:49", "height": 37.0, "n_samples": 539581, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -94.445681, 45.594327 ] } }, +{ "type": "Feature", "properties": { "site_id": 29, "wind_speed_mean": 6.4962501058368014, "time_start": "2010-11-04T12:40:16", "time_end": "2023-04-24T03:00:00", "height": 37.0, "n_samples": 519668, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -160.76903, 63.909604 ] } }, +{ "type": "Feature", "properties": { "site_id": 30, "wind_speed_mean": 6.3885694015524583, "time_start": "2011-08-30T08:56:46", "time_end": "2022-01-27T06:10:18", "height": 37.0, "n_samples": 407354, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.5868, 61.53757 ] } }, +{ "type": "Feature", "properties": { "site_id": 31, "wind_speed_mean": 6.5877880845785874, "time_start": "2010-11-05T14:05:23", "time_end": "2023-04-24T02:50:00", "height": 37.0, "n_samples": 456333, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -160.76923, 63.909982 ] } }, +{ "type": "Feature", "properties": { "site_id": 32, "wind_speed_mean": 6.4804331493078839, "time_start": "2010-11-04T12:40:16", "time_end": "2023-04-11T23:50:02", "height": 37.0, "n_samples": 420594, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -160.76946, 63.910369 ] } }, +{ "type": "Feature", "properties": { "site_id": 33, "wind_speed_mean": 6.4965346252377527, "time_start": "2010-11-04T12:40:16", "time_end": "2023-04-24T02:50:00", "height": 37.0, "n_samples": 491058, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -160.76966, 63.91085 ] } }, +{ "type": "Feature", "properties": { "site_id": 34, "wind_speed_mean": 6.2318387178136989, "time_start": "2011-08-30T08:56:46", "time_end": "2023-02-25T23:40:01", "height": 37.0, "n_samples": 451978, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.5868, 61.53757 ] } }, +{ "type": "Feature", "properties": { "site_id": 35, "wind_speed_mean": 6.2719135109957378, "time_start": "2011-08-30T08:56:46", "time_end": "2023-04-24T02:56:19", "height": 37.0, "n_samples": 352091, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.5868, 61.53757 ] } }, +{ "type": "Feature", "properties": { "site_id": 36, "wind_speed_mean": 3.848057561104854, "time_start": "2012-06-05T19:07:34", "time_end": "2021-05-18T21:20:01", "height": 30.0, "n_samples": 241552, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -149.444125, 60.110769 ] } }, +{ "type": "Feature", "properties": { "site_id": 37, "wind_speed_mean": 4.741370795259237, "time_start": "2011-03-11T19:47:31", "time_end": "2023-04-24T02:55:54", "height": 37.0, "n_samples": 547507, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -82.644979, 40.764318 ] } }, +{ "type": "Feature", "properties": { "site_id": 38, "wind_speed_mean": 4.1094881361597562, "time_start": "2011-03-03T10:20:00", "time_end": "2023-04-24T02:55:54", "height": 37.0, "n_samples": 442774, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -82.645783, 40.764012 ] } }, +{ "type": "Feature", "properties": { "site_id": 39, "wind_speed_mean": 5.1994935495950596, "time_start": "2010-10-27T15:20:10", "time_end": "2021-08-10T14:48:58", "height": 37.0, "n_samples": 544772, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -82.111822, 41.488629 ] } }, +{ "type": "Feature", "properties": { "site_id": 40, "wind_speed_mean": 5.0541276822308605, "time_start": "2011-03-08T15:44:50", "time_end": "2023-04-24T02:50:02", "height": 37.0, "n_samples": 540921, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.017104, 40.819456 ] } }, +{ "type": "Feature", "properties": { "site_id": 41, "wind_speed_mean": 5.2596310356836753, "time_start": "2010-10-28T15:29:41", "time_end": "2023-04-24T03:00:02", "height": 37.0, "n_samples": 404863, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -145.594962, 64.014433 ] } }, +{ "type": "Feature", "properties": { "site_id": 42, "wind_speed_mean": 4.1357928931920087, "time_start": "2010-10-26T15:36:50", "time_end": "2019-03-30T05:30:07", "height": 37.0, "n_samples": 330303, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -89.596725, 44.97932 ] } }, +{ "type": "Feature", "properties": { "site_id": 43, "wind_speed_mean": 5.5692815797650148, "time_start": "2010-10-20T13:31:40", "time_end": "2023-04-24T02:57:33", "height": 37.0, "n_samples": 608293, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -88.9548, 39.840315 ] } }, +{ "type": "Feature", "properties": { "site_id": 44, "wind_speed_mean": 4.4212480167045864, "time_start": "2010-10-26T13:46:03", "time_end": "2023-04-24T02:50:39", "height": 37.0, "n_samples": 534464, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -71.3937, 42.042508 ] } }, +{ "type": "Feature", "properties": { "site_id": 45, "wind_speed_mean": 5.1319003930976947, "time_start": "2010-10-26T13:46:03", "time_end": "2017-10-09T16:10:09", "height": null, "n_samples": 300943, "type": "vendor" }, "geometry": null }, +{ "type": "Feature", "properties": { "site_id": 47, "wind_speed_mean": 7.8619751142213685, "time_start": "2010-10-19T23:01:26", "time_end": "2023-04-24T03:00:02", "height": 37.0, "n_samples": 417829, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -72.834801, 44.414417 ] } }, +{ "type": "Feature", "properties": { "site_id": 48, "wind_speed_mean": 3.4955901752920742, "time_start": "2010-09-23T16:20:07", "time_end": "2023-04-24T03:00:02", "height": 37.0, "n_samples": 421468, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -71.590406, 44.398839 ] } }, +{ "type": "Feature", "properties": { "site_id": 62, "wind_speed_mean": 6.4509739026080215, "time_start": "2010-10-18T20:16:17", "time_end": "2023-04-24T02:50:45", "height": 37.0, "n_samples": 581629, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -91.887501, 42.563834 ] } }, +{ "type": "Feature", "properties": { "site_id": 63, "wind_speed_mean": 4.527411815673668, "time_start": "2010-10-28T16:58:58", "time_end": "2019-10-12T14:40:00", "height": 37.0, "n_samples": 394713, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -88.856767, 42.94108 ] } }, +{ "type": "Feature", "properties": { "site_id": 64, "wind_speed_mean": 5.2282701206460152, "time_start": "2010-10-20T16:16:06", "time_end": "2020-05-11T12:39:14", "height": 37.0, "n_samples": 430267, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -94.24849, 41.616557 ] } }, +{ "type": "Feature", "properties": { "site_id": 65, "wind_speed_mean": 6.6419806995868749, "time_start": "2010-10-29T02:28:10", "time_end": "2023-04-24T03:10:02", "height": 37.0, "n_samples": 564962, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -94.456018, 41.827683 ] } }, +{ "type": "Feature", "properties": { "site_id": 66, "wind_speed_mean": 4.1145515274014821, "time_start": "2010-10-21T16:19:48", "time_end": "2023-03-30T19:11:10", "height": 37.0, "n_samples": 488313, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -72.901845, 41.301228 ] } }, +{ "type": "Feature", "properties": { "site_id": 67, "wind_speed_mean": 6.0394800607024379, "time_start": "2010-10-19T16:03:25", "time_end": "2023-04-24T02:50:57", "height": 30.0, "n_samples": 357811, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -101.507541, 32.227597 ] } }, +{ "type": "Feature", "properties": { "site_id": 68, "wind_speed_mean": 5.4411761836929307, "time_start": "2010-10-28T02:38:34", "time_end": "2023-04-24T02:50:51", "height": 37.0, "n_samples": 594601, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -72.570855, 40.969017 ] } }, +{ "type": "Feature", "properties": { "site_id": 69, "wind_speed_mean": 4.3584720037447529, "time_start": "2010-10-21T16:59:21", "time_end": "2023-04-24T02:50:57", "height": 37.0, "n_samples": 497763, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -81.883051, 41.24813 ] } }, +{ "type": "Feature", "properties": { "site_id": 70, "wind_speed_mean": 5.1092743322858345, "time_start": "2010-10-26T18:19:29", "time_end": "2023-01-03T16:51:40", "height": 37.0, "n_samples": 566747, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -82.585897, 41.404191 ] } }, +{ "type": "Feature", "properties": { "site_id": 71, "wind_speed_mean": 4.8667527409621991, "time_start": "2011-05-19T15:32:12", "time_end": "2023-02-04T07:21:59", "height": 30.0, "n_samples": 400042, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.304785, 41.664307 ] } }, +{ "type": "Feature", "properties": { "site_id": 72, "wind_speed_mean": 4.6231403208175887, "time_start": "2011-08-17T19:11:48", "time_end": "2023-04-24T02:51:17", "height": 30.0, "n_samples": 528774, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.306444, 41.664371 ] } }, +{ "type": "Feature", "properties": { "site_id": 73, "wind_speed_mean": 3.6356548228849852, "time_start": "2010-10-26T13:46:02", "time_end": "2023-04-24T02:50:54", "height": 30.0, "n_samples": 627643, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -73.14599, 44.463175 ] } }, +{ "type": "Feature", "properties": { "site_id": 74, "wind_speed_mean": 5.656469082616856, "time_start": "2010-10-28T20:23:16", "time_end": "2023-04-24T03:53:49", "height": 18.0, "n_samples": 490711, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.66356, 39.54785 ] } }, +{ "type": "Feature", "properties": { "site_id": 75, "wind_speed_mean": 5.5513860990779502, "time_start": "2010-10-28T20:23:16", "time_end": "2023-04-24T03:53:49", "height": 18.0, "n_samples": 495852, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.66429, 39.54838 ] } }, +{ "type": "Feature", "properties": { "site_id": 76, "wind_speed_mean": 7.880008865248227, "time_start": "2013-05-06T20:30:01", "time_end": "2021-10-22T02:50:01", "height": 37.0, "n_samples": 6768, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -155.804475, 20.24052 ] } }, +{ "type": "Feature", "properties": { "site_id": 77, "wind_speed_mean": 4.8670045069078105, "time_start": "2010-12-22T03:42:03", "time_end": "2023-04-24T02:50:53", "height": 37.0, "n_samples": 515431, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -82.843286, 41.282586 ] } }, +{ "type": "Feature", "properties": { "site_id": 78, "wind_speed_mean": 6.1535545286652997, "time_start": "2014-08-26T17:50:00", "time_end": "2023-04-24T03:00:02", "height": 37.0, "n_samples": 378489, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -161.793531, 60.794064 ] } }, +{ "type": "Feature", "properties": { "site_id": 79, "wind_speed_mean": 7.1522008035754601, "time_start": "2016-07-16T14:40:00", "time_end": "2022-11-11T15:00:04", "height": 37.0, "n_samples": 287963, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -119.476494, 33.2309 ] } }, +{ "type": "Feature", "properties": { "site_id": 80, "wind_speed_mean": 6.2245632979585608, "time_start": "2011-08-30T08:56:46", "time_end": "2023-04-24T02:56:19", "height": 37.0, "n_samples": 419508, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -165.5868, 61.53757 ] } }, +{ "type": "Feature", "properties": { "site_id": 81, "wind_speed_mean": 4.5407143799289118, "time_start": "2010-10-29T02:56:06", "time_end": "2022-09-28T14:30:49", "height": 37.0, "n_samples": 576192, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -87.997436, 43.659363 ] } }, +{ "type": "Feature", "properties": { "site_id": 82, "wind_speed_mean": 5.4513954220708936, "time_start": "2010-10-27T12:58:20", "time_end": "2023-04-24T03:00:02", "height": 37.0, "n_samples": 508090, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.13483, 41.440643 ] } }, +{ "type": "Feature", "properties": { "site_id": 83, "wind_speed_mean": 6.0071230445129311, "time_start": "2010-10-27T01:44:28", "time_end": "2023-04-24T02:50:57", "height": 37.0, "n_samples": 512660, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.024261, 32.421447 ] } }, +{ "type": "Feature", "properties": { "site_id": 84, "wind_speed_mean": 4.685001132807753, "time_start": "2010-10-29T02:56:06", "time_end": "2023-04-25T02:50:16", "height": 37.0, "n_samples": 609106, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -87.997106, 43.658456 ] } }, +{ "type": "Feature", "properties": { "site_id": 85, "wind_speed_mean": 5.0297316165453205, "time_start": "2010-10-28T15:08:04", "time_end": "2023-04-25T02:52:23", "height": 37.0, "n_samples": 597317, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -82.802919, 41.399158 ] } }, +{ "type": "Feature", "properties": { "site_id": 86, "wind_speed_mean": 6.7225349376088408, "time_start": "2010-10-28T16:52:28", "time_end": "2023-04-25T02:50:28", "height": 37.0, "n_samples": 555848, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -99.428211, 36.406453 ] } }, +{ "type": "Feature", "properties": { "site_id": 87, "wind_speed_mean": 6.2706048416312692, "time_start": "2011-01-11T19:14:32", "time_end": "2023-04-25T03:00:02", "height": 37.0, "n_samples": 372643, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -88.291092, 40.150208 ] } }, +{ "type": "Feature", "properties": { "site_id": 88, "wind_speed_mean": 4.8119906876376328, "time_start": "2010-11-30T14:21:50", "time_end": "2023-04-25T02:54:10", "height": 37.0, "n_samples": 587606, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.144457, 41.690059 ] } }, +{ "type": "Feature", "properties": { "site_id": 89, "wind_speed_mean": 4.1350428450827028, "time_start": "2011-01-25T23:44:49", "time_end": "2021-12-23T20:41:14", "height": 37.0, "n_samples": 543703, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -81.045434, 40.868665 ] } }, +{ "type": "Feature", "properties": { "site_id": 90, "wind_speed_mean": 4.2384036849046947, "time_start": "2010-10-21T19:46:06", "time_end": "2023-04-25T02:50:45", "height": 30.0, "n_samples": 545143, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -73.155089, 44.442995 ] } }, +{ "type": "Feature", "properties": { "site_id": 91, "wind_speed_mean": 3.641073368934292, "time_start": "2010-12-23T18:26:17", "time_end": "2021-12-12T03:38:34", "height": 37.0, "n_samples": 430700, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -76.050206, 43.569283 ] } }, +{ "type": "Feature", "properties": { "site_id": 92, "wind_speed_mean": 4.8063049217522211, "time_start": "2010-10-28T15:53:29", "time_end": "2016-11-17T14:50:00", "height": 37.0, "n_samples": 288251, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -89.28119, 42.77613 ] } }, +{ "type": "Feature", "properties": { "site_id": 128, "wind_speed_mean": 4.2245049056047259, "time_start": "2011-04-18T20:05:52", "time_end": "2023-04-25T02:59:57", "height": 37.0, "n_samples": 501773, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -80.700809, 41.448017 ] } }, +{ "type": "Feature", "properties": { "site_id": 130, "wind_speed_mean": 5.9930495799653674, "time_start": "2011-03-29T20:05:19", "time_end": "2023-04-25T02:51:01", "height": 37.0, "n_samples": 529004, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.395712, 45.807594 ] } }, +{ "type": "Feature", "properties": { "site_id": 131, "wind_speed_mean": 6.128211770859548, "time_start": "2011-03-29T21:14:34", "time_end": "2020-03-29T05:10:04", "height": 37.0, "n_samples": 405306, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.396045, 45.807356 ] } }, +{ "type": "Feature", "properties": { "site_id": 132, "wind_speed_mean": 5.2254382412116414, "time_start": "2011-03-08T15:44:50", "time_end": "2023-04-25T03:00:02", "height": 37.0, "n_samples": 456769, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.017103, 40.819453 ] } }, +{ "type": "Feature", "properties": { "site_id": 133, "wind_speed_mean": 6.0407525601718701, "time_start": "2012-12-21T23:20:00", "time_end": "2023-04-25T03:00:26", "height": 37.0, "n_samples": 494791, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -155.794407, 19.916116 ] } }, +{ "type": "Feature", "properties": { "site_id": 134, "wind_speed_mean": 5.9113709380652013, "time_start": "2013-05-21T11:50:00", "time_end": "2023-01-27T04:50:09", "height": 37.0, "n_samples": 383161, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -101.81038, 35.19875 ] } }, +{ "type": "Feature", "properties": { "site_id": 135, "wind_speed_mean": 3.364699317857645, "time_start": "2012-12-17T20:10:00", "time_end": "2023-03-10T18:00:02", "height": 30.0, "n_samples": 270618, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -85.264395, 42.321996 ] } }, +{ "type": "Feature", "properties": { "site_id": 136, "wind_speed_mean": 4.4137131974041406, "time_start": "2013-03-10T09:50:56", "time_end": "2023-04-25T03:00:02", "height": 37.0, "n_samples": 408497, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -87.31764, 41.48484 ] } }, +{ "type": "Feature", "properties": { "site_id": 137, "wind_speed_mean": 4.6709045429023845, "time_start": "2012-12-20T21:00:00", "time_end": "2023-04-25T02:54:49", "height": 37.0, "n_samples": 317022, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -87.871386, 42.305667 ] } }, +{ "type": "Feature", "properties": { "site_id": 138, "wind_speed_mean": 4.4777414255412813, "time_start": "2011-02-21T22:35:22", "time_end": "2023-04-25T03:00:13", "height": 37.0, "n_samples": 499740, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.564908, 41.590039 ] } }, +{ "type": "Feature", "properties": { "site_id": 140, "wind_speed_mean": 5.0229575847379149, "time_start": "2011-01-04T22:18:44", "time_end": "2023-04-25T03:00:02", "height": 37.0, "n_samples": 539900, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -84.30848, 41.45907 ] } }, +{ "type": "Feature", "properties": { "site_id": 143, "wind_speed_mean": 4.0743225760944668, "time_start": "2010-10-28T16:27:34", "time_end": "2023-04-25T03:00:14", "height": 37.0, "n_samples": 623797, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.082221, 40.110754 ] } }, +{ "type": "Feature", "properties": { "site_id": 144, "wind_speed_mean": 5.0506837322717919, "time_start": "2011-01-10T19:07:21", "time_end": "2023-04-25T02:58:12", "height": 37.0, "n_samples": 484327, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -102.98332, 42.67972 ] } }, +{ "type": "Feature", "properties": { "site_id": 145, "wind_speed_mean": 3.9369119166118898, "time_start": "2010-11-22T15:38:11", "time_end": "2023-04-25T03:31:09", "height": 37.0, "n_samples": 638940, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -81.608194, 41.5075 ] } }, +{ "type": "Feature", "properties": { "site_id": 146, "wind_speed_mean": 7.2499512267062753, "time_start": "2012-05-09T02:18:48", "time_end": "2021-11-27T21:22:12", "height": 37.0, "n_samples": 474645, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -161.2079, 64.361382 ] } }, +{ "type": "Feature", "properties": { "site_id": 147, "wind_speed_mean": 3.7909664697019139, "time_start": "2011-02-10T13:38:49", "time_end": "2023-04-26T02:51:04", "height": 37.0, "n_samples": 576076, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -71.466786, 41.724633 ] } }, +{ "type": "Feature", "properties": { "site_id": 148, "wind_speed_mean": 6.1445202196453037, "time_start": "2010-10-28T03:29:24", "time_end": "2023-01-31T13:00:16", "height": 37.0, "n_samples": 594595, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.097861, 41.272403 ] } }, +{ "type": "Feature", "properties": { "site_id": 150, "wind_speed_mean": 4.3543515677758071, "time_start": "2011-06-11T13:24:08", "time_end": "2023-04-26T03:00:34", "height": 37.0, "n_samples": 531613, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -81.502408, 41.414998 ] } }, +{ "type": "Feature", "properties": { "site_id": 151, "wind_speed_mean": 3.6020983067677919, "time_start": "2011-07-17T14:38:07", "time_end": "2023-04-26T02:51:00", "height": 37.0, "n_samples": 584090, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -86.89235, 40.429231 ] } }, +{ "type": "Feature", "properties": { "site_id": 152, "wind_speed_mean": 3.5817278217697961, "time_start": "2011-07-17T14:38:07", "time_end": "2023-04-26T02:51:00", "height": 37.0, "n_samples": 591774, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -86.89235, 40.429231 ] } }, +{ "type": "Feature", "properties": { "site_id": 153, "wind_speed_mean": 6.0028543862847172, "time_start": "2011-06-23T20:49:03", "time_end": "2023-04-26T03:00:01", "height": 37.0, "n_samples": 449834, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -107.240137, 41.801249 ] } }, +{ "type": "Feature", "properties": { "site_id": 154, "wind_speed_mean": 3.4174008910979685, "time_start": "2011-07-17T14:48:07", "time_end": "2023-04-26T02:51:00", "height": 37.0, "n_samples": 592303, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -86.89235, 40.429231 ] } }, +{ "type": "Feature", "properties": { "site_id": 157, "wind_speed_mean": 6.5760100019794816, "time_start": "2011-08-13T11:13:54", "time_end": "2022-11-22T00:30:02", "height": 30.0, "n_samples": 535494, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -76.477654, 24.226347 ] } }, +{ "type": "Feature", "properties": { "site_id": 159, "wind_speed_mean": 6.9883704120450254, "time_start": "2011-08-13T11:13:54", "time_end": "2022-11-22T00:30:02", "height": 30.0, "n_samples": 536786, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -76.477654, 24.224845 ] } }, +{ "type": "Feature", "properties": { "site_id": 160, "wind_speed_mean": 6.0060601494489827, "time_start": "2011-10-06T12:57:15", "time_end": "2023-04-26T03:00:00", "height": 37.0, "n_samples": 584012, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -100.679537, 46.084126 ] } }, +{ "type": "Feature", "properties": { "site_id": 161, "wind_speed_mean": 5.3739653443817907, "time_start": "2011-10-16T16:17:36", "time_end": "2023-04-26T02:58:57", "height": 30.0, "n_samples": 549579, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -109.550556, 44.204722 ] } }, +{ "type": "Feature", "properties": { "site_id": 163, "wind_speed_mean": 4.5984855055359448, "time_start": "2012-01-19T10:57:41", "time_end": "2023-04-26T03:00:20", "height": 37.0, "n_samples": 546519, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -73.263583, 44.172383 ] } }, +{ "type": "Feature", "properties": { "site_id": 164, "wind_speed_mean": 5.7584166750850256, "time_start": "2011-09-23T14:18:02", "time_end": "2023-01-09T10:31:33", "height": 37.0, "n_samples": 237576, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -71.489886, 41.380275 ] } }, +{ "type": "Feature", "properties": { "site_id": 165, "wind_speed_mean": 5.0117027324654861, "time_start": "2011-09-06T18:50:10", "time_end": "2023-04-26T03:00:03", "height": 37.0, "n_samples": 446776, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -87.983411, 43.548564 ] } }, +{ "type": "Feature", "properties": { "site_id": 167, "wind_speed_mean": 4.7271401118099243, "time_start": "2011-09-23T21:20:02", "time_end": "2023-04-26T03:00:01", "height": 37.0, "n_samples": 469368, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.435299, 35.902492 ] } }, +{ "type": "Feature", "properties": { "site_id": 169, "wind_speed_mean": 4.5390549692690909, "time_start": "2012-02-23T16:49:30", "time_end": "2023-04-26T03:00:04", "height": 37.0, "n_samples": 542776, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.146958, 33.21095 ] } }, +{ "type": "Feature", "properties": { "site_id": 170, "wind_speed_mean": 6.1060541313213559, "time_start": "2011-10-31T17:06:06", "time_end": "2023-04-04T14:01:22", "height": 37.0, "n_samples": 533074, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -88.255658, 39.819811 ] } }, +{ "type": "Feature", "properties": { "site_id": 171, "wind_speed_mean": 4.5153166507776445, "time_start": "2012-02-23T16:49:30", "time_end": "2023-04-27T03:00:04", "height": 37.0, "n_samples": 544401, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.146958, 33.21095 ] } }, +{ "type": "Feature", "properties": { "site_id": 172, "wind_speed_mean": 4.7184893882418013, "time_start": "2012-02-23T16:49:30", "time_end": "2023-04-27T03:00:04", "height": 37.0, "n_samples": 545951, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.146958, 33.21095 ] } }, +{ "type": "Feature", "properties": { "site_id": 173, "wind_speed_mean": 6.0218016263887302, "time_start": "2011-11-21T22:23:49", "time_end": "2023-04-27T02:51:01", "height": 37.0, "n_samples": 401626, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -87.843806, 43.857461 ] } }, +{ "type": "Feature", "properties": { "site_id": 176, "wind_speed_mean": 5.5229844228318541, "time_start": "2012-06-19T20:55:28", "time_end": "2023-04-27T02:54:59", "height": 37.0, "n_samples": 509271, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.22068, 41.38836 ] } }, +{ "type": "Feature", "properties": { "site_id": 177, "wind_speed_mean": 5.5297722792361137, "time_start": "2011-12-01T03:12:55", "time_end": "2023-04-27T02:50:54", "height": 37.0, "n_samples": 507771, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -72.466025, 41.030689 ] } }, +{ "type": "Feature", "properties": { "site_id": 179, "wind_speed_mean": 4.0796686927075259, "time_start": "2012-01-30T11:55:41", "time_end": "2023-04-27T02:50:47", "height": 37.0, "n_samples": 393049, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -70.725695, 41.974071 ] } }, +{ "type": "Feature", "properties": { "site_id": 180, "wind_speed_mean": 7.0178989629278252, "time_start": "2011-12-14T13:17:28", "time_end": "2023-02-21T16:20:02", "height": 30.0, "n_samples": 522818, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -76.477654, 24.226347 ] } }, +{ "type": "Feature", "properties": { "site_id": 181, "wind_speed_mean": 5.2728111509988587, "time_start": "2011-12-22T20:38:38", "time_end": "2023-04-27T02:51:06", "height": 37.0, "n_samples": 470487, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -91.617981, 43.037707 ] } }, +{ "type": "Feature", "properties": { "site_id": 182, "wind_speed_mean": 3.1706057248257022, "time_start": "2012-02-01T17:10:47", "time_end": "2023-04-27T03:00:03", "height": 37.0, "n_samples": 435891, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -80.587032, 41.423483 ] } }, +{ "type": "Feature", "properties": { "site_id": 183, "wind_speed_mean": 3.1391679831799526, "time_start": "2012-02-01T17:10:47", "time_end": "2023-04-27T03:00:03", "height": 37.0, "n_samples": 340546, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -80.586595, 41.422738 ] } }, +{ "type": "Feature", "properties": { "site_id": 184, "wind_speed_mean": 6.7082067219996038, "time_start": "2012-02-24T12:44:33", "time_end": "2023-04-27T02:58:50", "height": 37.0, "n_samples": 546028, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -73.58814, 40.593284 ] } }, +{ "type": "Feature", "properties": { "site_id": 185, "wind_speed_mean": 5.6584532707269526, "time_start": "2012-02-22T19:28:15", "time_end": "2023-04-27T03:00:11", "height": 37.0, "n_samples": 496648, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -96.707544, 28.412547 ] } }, +{ "type": "Feature", "properties": { "site_id": 187, "wind_speed_mean": 3.3867425711001871, "time_start": "2012-03-19T13:22:26", "time_end": "2023-02-13T04:50:01", "height": 37.0, "n_samples": 435899, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -81.94, 41.32 ] } }, +{ "type": "Feature", "properties": { "site_id": 188, "wind_speed_mean": 5.4944871743628099, "time_start": "2012-06-19T20:55:28", "time_end": "2023-04-27T02:54:59", "height": 37.0, "n_samples": 525393, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -83.22083, 41.38761 ] } }, +{ "type": "Feature", "properties": { "site_id": 194, "wind_speed_mean": 5.2333991540449771, "time_start": "2012-04-24T08:45:39", "time_end": "2019-06-25T04:39:55", "height": 37.0, "n_samples": 367159, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -97.486807, 25.897108 ] } }, +{ "type": "Feature", "properties": { "site_id": 195, "wind_speed_mean": 5.2333991540449771, "time_start": "2012-04-24T08:45:39", "time_end": "2019-06-25T04:39:55", "height": 37.0, "n_samples": 367159, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -87.890936, 43.00345 ] } }, +{ "type": "Feature", "properties": { "site_id": 197, "wind_speed_mean": 3.781980809448132, "time_start": "2012-03-23T17:53:50", "time_end": "2023-04-27T03:00:02", "height": 37.0, "n_samples": 517859, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -69.101227, 44.190127 ] } }, +{ "type": "Feature", "properties": { "site_id": 199, "wind_speed_mean": 4.3501934209138797, "time_start": "2012-05-09T00:07:07", "time_end": "2023-04-27T03:00:02", "height": 37.0, "n_samples": 540531, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -95.713739, 39.068026 ] } }, +{ "type": "Feature", "properties": { "site_id": 234, "wind_speed_mean": 4.1793229726253331, "time_start": "2012-12-20T18:10:00", "time_end": "2023-04-27T02:51:18", "height": 37.0, "n_samples": 528664, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -93.636661, 42.028437 ] } }, +{ "type": "Feature", "properties": { "site_id": 237, "wind_speed_mean": 4.019276440409568, "time_start": "2013-03-07T18:50:00", "time_end": "2023-04-27T02:50:48", "height": 37.0, "n_samples": 463514, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -73.310783, 44.002848 ] } }, +{ "type": "Feature", "properties": { "site_id": 238, "wind_speed_mean": 4.5377825377440164, "time_start": "2014-02-04T18:30:00", "time_end": "2023-04-27T03:00:01", "height": 37.0, "n_samples": 472459, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -79.586944, 42.339167 ] } }, +{ "type": "Feature", "properties": { "site_id": 241, "wind_speed_mean": 4.7264712100196444, "time_start": "2013-09-10T18:00:00", "time_end": "2022-10-25T22:41:13", "height": 37.0, "n_samples": 387878, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -77.798678, 42.260615 ] } }, +{ "type": "Feature", "properties": { "site_id": 261, "wind_speed_mean": 7.2435023853972211, "time_start": "2016-07-16T14:40:00", "time_end": "2022-11-11T15:00:04", "height": 37.0, "n_samples": 231408, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -119.475583, 33.231219 ] } }, +{ "type": "Feature", "properties": { "site_id": 262, "wind_speed_mean": 6.7829031331813194, "time_start": "2016-07-16T14:40:00", "time_end": "2022-11-11T15:00:04", "height": 37.0, "n_samples": 296025, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -119.474306, 33.232058 ] } }, +{ "type": "Feature", "properties": { "site_id": 263, "wind_speed_mean": 6.9650950467331922, "time_start": "2016-07-16T14:40:00", "time_end": "2022-10-30T20:19:17", "height": 37.0, "n_samples": 221470, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -119.472706, 33.231742 ] } }, +{ "type": "Feature", "properties": { "site_id": 264, "wind_speed_mean": 6.9438519855388208, "time_start": "2016-07-18T14:30:00", "time_end": "2020-11-07T16:50:06", "height": 37.0, "n_samples": 521949, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -119.471533, 33.2315 ] } }, +{ "type": "Feature", "properties": { "site_id": 388, "wind_speed_mean": 5.7143441768960601, "time_start": "2014-10-30T03:30:00", "time_end": "2023-04-28T03:00:00", "height": 37.0, "n_samples": 1065394, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -74.008761, 40.663248 ] } }, +{ "type": "Feature", "properties": { "site_id": 405, "wind_speed_mean": 5.6582053571750057, "time_start": "2015-06-26T05:40:00", "time_end": "2021-07-28T18:50:00", "height": 37.0, "n_samples": 555442, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -161.287, 65.965 ] } }, +{ "type": "Feature", "properties": { "site_id": 418, "wind_speed_mean": 4.280100614940932, "time_start": "2015-01-01T10:50:00", "time_end": "2023-04-28T03:00:23", "height": 37.0, "n_samples": 773245, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -78.337222, 42.928889 ] } }, +{ "type": "Feature", "properties": { "site_id": 422, "wind_speed_mean": 4.6594099084061122, "time_start": "2015-03-21T10:20:00", "time_end": "2023-04-28T03:00:01", "height": 37.0, "n_samples": 727887, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -73.250154, 44.183341 ] } }, +{ "type": "Feature", "properties": { "site_id": 430, "wind_speed_mean": 4.0809681491215946, "time_start": "2015-11-20T00:10:00", "time_end": "2023-05-01T03:00:17", "height": 37.0, "n_samples": 578320, "type": "vendor" }, "geometry": { "type": "Point", "coordinates": [ -78.33425, 42.929153 ] } } +] +} diff --git a/powercurve.py b/powercurve.py index 733d412..23689d5 100644 --- a/powercurve.py +++ b/powercurve.py @@ -55,8 +55,9 @@ def windspeed_to_kw(self, df, ws_column="ws-adjusted", dt_column="datetime", tri below_curve = df[kw < 0] above_curve = df[kw > self.max_ws] - self.below_curve.extend(zip(below_curve[dt_column].tolist(), below_curve[ws_column].tolist())) - self.above_curve.extend(zip(above_curve[dt_column].tolist(), above_curve[ws_column].tolist())) + if dt_column in df.columns: + self.below_curve.extend(zip(below_curve[dt_column].tolist(), below_curve[ws_column].tolist())) + self.above_curve.extend(zip(above_curve[dt_column].tolist(), above_curve[ws_column].tolist())) return kw diff --git a/proto-DEPRECATED-073124.py b/proto-DEPRECATED-073124.py new file mode 100644 index 0000000..2963f41 --- /dev/null +++ b/proto-DEPRECATED-073124.py @@ -0,0 +1,1623 @@ +from flask import Flask, render_template, request, jsonify, Response +from flask_cors import CORS +from threading import Thread +from time import sleep +import time +import datetime +import json +import pandas as pd +import argparse +import asyncio +import calendar +import hashlib +import shutil +import os +import requests +import urllib +import glob +import calendar +import matplotlib +import matplotlib.pyplot as plt +matplotlib.use('agg') + +from dw_tap.data_fetching import getData +from v2 import validated_params_v2_w_year +from v2 import validated_params_v2 +from v2 import validated_latlon +from hsds_helpers import connected_hsds_file +from bc import bc_for_point +from infomap import get_infomap_script +from windrose import WindroseAxes +from powercurve import PowerCurve +from shapely.geometry import Point +import geopandas as gpd +import heapq +import scipy.interpolate +import numpy as np +from html_maker import * +import flask + +# Start of WTK-LED fucntions +# Code below is copied from the uncertainty notebook by Caleb Phillips +# This function fetches the grid point index and returns as an indexed geodataframe +def get_index(): + # For sample: + #base_url = "https://wtk-csv-testing-dav-sandbox.s3.us-west-2.amazonaws.com" + base_url = "https://wtk-led.s3.us-west-2.amazonaws.com" + file_path = f"/location_index.csv.gz" + index = gpd.GeoDataFrame(pd.read_csv(base_url+file_path)) + index['location'] = gpd.GeoSeries(gpd.points_from_xy(index.longitude, index.latitude)) + return index +wtkled_index = get_index() + +# This function finds the closest grid point to the given lat/lon +# Note that a faster method might use index.location.sindex, the STRTree optimized +# spatial index, but support is not universal and depends on specific versions of Geos and PyGeos: +# https://pygeos.readthedocs.io/en/latest/strtree.html +# Hence, we're doing the more universally supported, but less optimized thing here +def closest_grid_point(index,lat,lon): + p = Point(lon,lat) + r = index.iloc[index.location.distance(p).argmin()].copy() + r['distance_degrees'] = Point(r['longitude'],r['latitude']).distance(p) + return r + +def parse_mohr(mohr): + s = str(mohr) + m, h = s[:-2], s[-2:] + return int(m), int(h) + +def get_1224(idx, year=2020, add_year_col=False): + # For sample: + #base_url = "https://wtk-csv-testing-dav-sandbox.s3.us-west-2.amazonaws.com/1224" + base_url = "https://wtk-led.s3.us-west-2.amazonaws.com/1224" + file_path = f"/year={year}/varset=all/index={idx}/{idx}_{year}_all.csv.gz" + res = pd.read_csv(base_url + file_path) + res["m"], res["h"] = zip(*res["mohr"].apply(parse_mohr)) + if add_year_col: + res["year"] = year + return res + +def get_1224_20yrs(idx, ws_col_for_estimating_power, selected_powercurve, relevant_columns_only=True): + # Example of the dataframe returned by this func (wtih relevant_columns_only=True): + # + # year mohr month h windspeed_40m windspeed_40m_kw winddirection_40m + # 0 2001 101 Jan 1 4.31 10.765848 334.29 + # 1 2001 102 Jan 2 5.07 17.058348 323.40 + # 2 2001 103 Jan 3 5.38 20.721432 302.85 + + df = pd.concat([get_1224(idx, year=yr, add_year_col=True) for yr in range(2001, 2021)]) + + df.rename(columns={"m": "month"}, inplace=True) + # df.month = df.month.apply(lambda x: calendar.month_abbr[x]) -- converting months to Jan, Feb, etc. should happen last to avoid messing up the other of months + + if ws_col_for_estimating_power in df.columns: + df[ws_col_for_estimating_power + "_kw"] = selected_powercurve.windspeed_to_kw(df, ws_col_for_estimating_power) + if relevant_columns_only: + wd_col = ws_col_for_estimating_power.replace("speed", "direction") + relevant_columns = ["year", "mohr", "month", "h", ws_col_for_estimating_power, ws_col_for_estimating_power + "_kw", wd_col] + return df[relevant_columns] + else: + return df + +def df2yearly_avg(df, ws_column, kw_column): + tmp = df.drop(columns=["mohr", "month", "h"] + [x for x in df.columns if "winddirection" in x]) + #res = tmp.groupby("year").agg("mean") + res = tmp.groupby("year").agg(avg_ws=(ws_column, "mean"), kwh_total=(kw_column, "sum")) # kwh_total will sum for all months and all hours + + res["kwh_total"] = res["kwh_total"] * 30 # Coarse estimation: 30 days in every month; no need to /20.0 for individual years + res.rename(columns={"avg_ws": "Average wind speed, m/s", "kwh_total": "kWh produced"}, inplace=True) + + # res["kWh produced"] = res["kWh produced"].astype(float).map('{:,.0f}'.format) + # res["Average wind speed, m/s"] = res["Average wind speed, m/s"].astype(float).map('{:,.2f}'.format) + + # Return entired dataframe, with all years + # return res + + # Create df with low, avg, high year + + res = res.sort_values("Average wind speed, m/s") + #return res + + res_avg = pd.DataFrame(res.mean()).T + res_avg.index=["Average year"] + #return res_avg + + res_3years = pd.concat([res.iloc[[0]], res_avg, res.iloc[[-1]]]) + res_3years["kWh produced"] = res_3years["kWh produced"].astype(float).map('{:,.0f}'.format) + res_3years["Average wind speed, m/s"] = res_3years["Average wind speed, m/s"].astype(float).map('{:,.2f}'.format) + + res_3years.index = ["Lowest year (%s)" % str(res_3years.index[0]), \ + res_3years.index[1], \ + "Highest year (%s)" % str(res_3years.index[2])] + + return res_3years + +def df2monthly_avg(df, ws_column, kw_column): + tmp = df.drop(columns=["year", "mohr", "h"] + [x for x in df.columns if "winddirection" in x]) + #res = tmp.groupby("month").agg("mean") + res = tmp.groupby("month").agg(avg_ws=(ws_column, "mean"), kwh_total=(kw_column, "sum")) # kwh_total will sum for all hours and all years + + res["kwh_total"] = res["kwh_total"] * 30 / 20.0 # Coarse estimation: 30 days in every month & 20 years + res.rename(columns={"avg_ws": "Average wind speed, m/s", "kwh_total": "kWh produced"}, inplace=True) + res.index = pd.Series(res.index).apply(lambda x: calendar.month_abbr[x]) + + res["kWh produced"] = res["kWh produced"].astype(float).map('{:,.0f}'.format) + res["Average wind speed, m/s"] = res["Average wind speed, m/s"].astype(float).map('{:,.2f}'.format) + + return res + +def yearly_avg_df_to_closest_heights(yearly_avg, selected_height, heights_count=1): + # Use columns that are of format: windspeed_XYZm where XYZ are integers + all_cols = yearly_avg.columns + heights = [int(c.split("_")[1].rstrip("m")) for c in yearly_avg.columns if "windspeed" in c] + #return heights + + closest_heights = heapq.nsmallest(heights_count, heights, key=lambda x: abs(x-selected_height)) + + closest_heights_columns = [] + for h in closest_heights: + c = "windspeed_%dm" % h + if c in all_cols: + closest_heights_columns.append(c) + # Leave out winddirection columns for now + # c = "winddirection_%dm" % h + # if c in all_cols: + # closest_heights_columns.append(c) + + return closest_heights, closest_heights_columns + +def get_uncertainty_dataframe(location,height=40): + # For sample: + #base_url = "https://wtk-csv-testing-dav-sandbox.s3.us-west-2.amazonaws.com/uncertainty" + base_url = "https://wtk-led.s3.us-west-2.amazonaws.com/uncertainty" + file_path = f"/height={height}/index={location}/{location}_{height}m.csv.gz" + return pd.read_csv(base_url+file_path) + +# End of WTK-LED fucntions + + +def feasibility_thresh_by_height(h): + """ + From literature (shared by Heidi): + For a small wind turbine hub height of 30 m, 4.0 m/s (9 mph) + is typically cited as the minimum annual average wind speed + required for a feasible project. + For a large wind turbine hub height of 80 m, + a minimum annual average wind speed of 6.5 m/s (14.5 mph) is typically needed. + """ + y_interp = scipy.interpolate.interp1d([30, 80], [4.0, 6.5], fill_value="extrapolate") + return y_interp(h) + +server_started_at = datetime.datetime.now() + +if "GOOGLE_MAPS_API_KEY" in os.environ: + given_google_maps_api_key = os.environ.get('GOOGLE_MAPS_API_KEY') +else: + given_google_maps_api_key = "" + +# Necessary directories: create if not there +outputs_dir = "outputs" +if not os.path.exists(outputs_dir): + os.mkdir(outputs_dir) + +# pending_dir = "static/pending/" +# if not os.path.exists(pending_dir): +# os.mkdir(pending_dir) +completed_dir = "static/completed/" +if not os.path.exists(completed_dir): + os.mkdir(completed_dir) + +templates_dir = "templates" +if not os.path.exists("%s/served" % templates_dir): + os.mkdir("%s/served" % templates_dir) + +# Csv and png outputs will be saved in the following dirs; +# Having them live inside static is important becuase it makes them accessible to the outside +csv_dir = "static/raw" +if not os.path.exists(csv_dir): + os.mkdir(csv_dir) + +plot_dir = "static/plots/" +if not os.path.exists(plot_dir): + os.mkdir(plot_dir) + +powercurves_dir = "powercurves" +# Create dict with keys being names of all available power curves (without extensions) +# and values being the corresponding PowerCurve objects +powercurves = {} +powercurve_default = "" +for fname in glob.glob(powercurves_dir + '/*.csv'): + powercurve_name = os.path.basename(fname).replace(".csv", "") + powercurves[powercurve_name] = PowerCurve(fname) + if powercurve_name == "nrel-reference-100kW": + powercurve_default = powercurve_name +if not powercurve_default: + powercurve_default = powercurves.keys()[0] + +# def instantiate_from_template(src, dest, old_text, new_text): +# """ Copy src file to dest with replacement of old_text with new_text """ +# # This version performs single substring replacement: old_text -> new_text +# +# fin = open(src) +# fout = open(dest, "wt") +# for line in fin: +# fout.write(line.replace(old_text, new_text)) +# fin.close() +# fout.close() + +def instantiate_from_template(src, dest, replacements): + """ Copy src file to dest with replacement of old_text with new_text """ + + # This version performs multiple substring replacement, using each tuple provided in the replacements list + # Each element there is: old_text -> new_text + fin = open(src) + fout = open(dest, "wt") + for line in fin: + updated_line = line + for r in replacements: + old_text, new_text = r[0], r[1] + updated_line = updated_line.replace(old_text, new_text) + fout.write(updated_line) + fin.close() + fout.close() + +def plot_monthly_avg(atmospheric_df, ws_column="ws", datetime_column="datetime", + title="Windspeed monthly averages", + show_avg_across_years=True, + label_avg_across_years=True, + save_to_file=True, + show_overall_avg=True, + show=True): + + if ws_column not in atmospheric_df.columns: + raise ValueError("Can't find %s column in dataframe. Skipping plotting" % ws_column) + if datetime_column not in atmospheric_df.columns: + raise ValueError("Can't find %s column in dataframe. Skipping plotting" % datetime_column) + + df = atmospheric_df[[datetime_column, ws_column]].copy() + + year_month = pd.Series(pd.PeriodIndex(df[datetime_column], freq="M")) + df["month"] = year_month.apply(lambda x: x.month) + df["year"] = year_month.apply(lambda x: x.year) + #display(df) + + fig, ax = plt.subplots(figsize=(10, 3)) + xvals = list(range(1, 13)) # for showing monthly data + + nyears = df["year"].nunique() + if nyears > 1: + for year, grp in df.groupby("year"): + monthly_avg = grp[[ws_column, "month"]].groupby("month").agg("mean") + ax.plot(monthly_avg, label=str(year), linestyle="--", alpha=0.4) + + if show_avg_across_years: + monthly_avg_across_years = df.groupby("month")[ws_column].agg("mean") + ax.plot(monthly_avg_across_years, label="Avg across years (labeled)", marker="o") + if label_avg_across_years: + ylim0 = ax.get_ylim()[0] + ylim1 = ax.get_ylim()[1] + yoffset = ylim1 / 20 # express offest as a fraction of height + yvals = pd.Series(monthly_avg_across_years.tolist()) + a = pd.concat({'x': pd.Series(xvals), + 'y': yvals, + 'val': yvals}, axis=1) + for i, point in a.iterrows(): + t = ax.text(point['x'], point['y'] + yoffset, "%.2f" % point['val'], fontsize=7) + t.set_bbox(dict(facecolor='lightgray', alpha=0.75, edgecolor='red')) + ax.set_ylim([ylim0, ylim1*1.25]) + else: + single_year = df["year"].tolist()[0] + monthly_avg_across_years = df.groupby("month")[ws_column].agg("mean") + ax.plot(monthly_avg_across_years, linestyle="--", label="Year: " + str(single_year), marker="o") + if label_avg_across_years: + ylim0 = ax.get_ylim()[0] + ylim1 = ax.get_ylim()[1] + yoffset = ylim1 / 20 # express offest as a fraction of height + yvals = pd.Series(monthly_avg_across_years.tolist()) + a = pd.concat({'x': pd.Series(xvals), + 'y': yvals, + 'val': yvals}, axis=1) + for i, point in a.iterrows(): + t = ax.text(point['x'], point['y'] + yoffset, "%.2f" % point['val'], fontsize=7) + t.set_bbox(dict(facecolor='lightgray', alpha=0.75, edgecolor='red')) + ax.set_ylim([ylim0, ylim1*1.25]) + + ax.set_xticks(xvals) + ax.set_xticklabels(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]) + + ax.set_ylabel("Monthly avg windspeed, m/s") + ax.set_title(title) + + # plt.figtext(0.1, -0.05, + # "Code used to produce this figure is developed under NREL's TAP project " + # "(https://www.nrel.gov/wind/tools-assessing-performance.html)", + # ha="left", fontsize=6) + + # Shrink current axis by 20% + box = ax.get_position() + ax.set_position([box.x0, box.y0, box.width * 0.8, box.height]) + + # Put a legend to the right of the current axis + ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)); + + if show_overall_avg: + ax.set_xlim([0, 16]) + overall_avg = df[ws_column].mean() + ax.axhline(y=overall_avg,linestyle="dotted", color="orange", linewidth=2.0) + t = ax.text(13.0, overall_avg + yoffset, "Overall avg=%.2f" % overall_avg, fontsize=8) + t.set_bbox(dict(facecolor='orange', alpha=0.3, edgecolor='black')) + + if save_to_file == True: + plt.savefig('%s.png' % title, dpi=300) + elif type(save_to_file) == str: + plt.savefig(save_to_file, dpi=300) + + if show: + plt.show() + +def plot_windrose(df, ws_column="ws", wd_column="wd", save_to_file=True): + ax = WindroseAxes.from_ax() + ax.bar(df[wd_column], df[ws_column], normed=True, opening=0.8, edgecolor='white') + ax.set_legend() + if save_to_file == True: + plt.savefig('%s.png' % title, dpi=300) + elif type(save_to_file) == str: + plt.savefig(save_to_file, dpi=300) + +def timeseries_to_12_by_24(df, styling=True, format="html"): + res = df + + res["datetime"] = pd.to_datetime(res["datetime"]) + res["hour"] = res["datetime"].apply(lambda x: x.hour) + res["month"] = res["datetime"].apply(lambda x: x.month) + res = res.groupby(["hour", "month"]).agg(ws_avg=("ws", "mean")) + res = res.stack().unstack(level=1) + res.index = ["Hour " + str(el[0]) for el in res.index] # range(len(res)) + res.columns.name = "" + res.rename(columns={c:calendar.month_name[c][:3] for c in res.columns}, inplace=True) + + min_ws = res.min().min() + max_ws = res.max().max() + overall_mean = res.mean().mean() + + if styling: + res = res.style.background_gradient("BuPu", axis=1).format(precision=3).set_caption("Overall average: %.3f" % overall_mean) + if format=="dataframe": + return res + elif format=="html": + #html = res.to_html(classes='12_by_24') + # Add colormap image + #html += "

Min=%.3f

" % (min_ws) + + html = "
%s
" % (res.to_html(table_id='12_by_24')) + + # + # Min=%.3f + # Max=%.3f + # + # + # + # + + # """ % (min_ws, max_ws) + + + return html + else: + return res + +with open('config.json', 'r') as f: + config = json.load(f) + +parser = argparse.ArgumentParser() +parser.add_argument('-p', '--production', action='store_true') +parser.add_argument('-d', '--development', action='store_true') +args = parser.parse_args() + +# Make development the default mode +development_mode = True +# Switch it if: "-p" and no "-d" +if (not args.development) and (args.production): + development_mode = False + +# Use parameters that are appropriate for the selected mode +if development_mode: + host = config["development"]["host"] + port = config["development"]["port"] +else: + host = config["production"]["host"] + port = config["production"]["port"] + +# # Identify the current environment +# # URL_prefix should NOT include "/" at the end, otherwise there will be errors +# if os.environ.get('AWS_EXECUTION_ENV') == "AWS_ECS_EC2": +# running_in_aws = True +# if port == 80 or port == "80": +# URL_prefix = "https://dw-tap.nrel.gov" +# else: +# URL_prefix = "https://dw-tap.nrel.gov:%s" % str(port) +# else: +# running_in_aws = False +# # This case is for running locally (container should be accessed via port 8080 even though inside it the server runs on part 80) +# URL_prefix = "http://localhost:8080" + +# Identify the current environment +# URL_prefix should NOT include "/" at the end, otherwise there will be errors +if os.environ.get('ENV') == "prod": + running_in_aws = True + if port == 80 or port == "80": + URL_prefix = "https://dw-tap.nrel.gov" + else: + URL_prefix = "https://dw-tap.nrel.gov:%s" % str(port) +elif os.environ.get('ENV') == "stage": + running_in_aws = True + if port == 80 or port == "80": + URL_prefix = "https://dw-tap-stage.stratus.nrel.gov" + else: + URL_prefix = "https://dw-tap-stage.stratus.nrel.gov:%s" % str(port) +elif os.environ.get('ENV') == "dev": + running_in_aws = True + if port == 80 or port == "80": + URL_prefix = "https://dw-tap-dev.stratus.nrel.gov" + else: + URL_prefix = "https://dw-tap-dev.stratus.nrel.gov:%s" % str(port) +else: + running_in_aws = False + # This case is for running locally (container should be accessed via port 8080 even though inside it the server runs on part 80) + URL_prefix = "http://localhost:8080" + + + + +# Now that URL_prefix is determined for the current env, prepare templates from universal ones +# Universal here means that those template can be used for AWS and non-AWS envs +src_dest_names = [("universal_monthly_index.html", "monthly_index.html"),\ + ("universal_windrose_index.html", "windrose_index.html"),\ + ("universal_12x24_index.html", "12x24_index.html"),\ + ("universal_ts_index.html", "ts_index.html"),\ + ("universal_bc_index.html", "bc_index.html"),\ + ("universal_info.html", "info.html"),\ + ("universal_on_map.html", "on_map.html"),\ + ("universal_on_map3.html", "on_map3.html"),\ + ("universal_by_address.html", "by_address.html"),\ + ("universal_kwh_index.html", "kwh_index.html"),\ + ("universal_energy.html", "energy.html")] +for src_dest in src_dest_names: + t_src, t_dest = src_dest[0], src_dest[1] + t_src = os.path.join(templates_dir, t_src) + t_dest = os.path.join(templates_dir, t_dest) + if os.path.exists(t_src): + instantiate_from_template(t_src,\ + t_dest, \ + [("URL_PREFIX", URL_prefix)]) + +app = Flask(__name__) +app.config["DEBUG"] = False +# Note: may consider limiting CORS for production deployment +# this opens up to AJAX calls from any domain +cors = CORS(app) + +output = "" +req_args = {} + +def serve_12x24(req_id, req_args): + output_dest = os.path.join(outputs_dir, req_id) + try: + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + f = connected_hsds_file(req_args, config) + + # Time index can be obtained from f but reading it from a previously saved file is faster + dt = pd.read_csv("wtk-dt.csv") + dt["datetime"] = pd.to_datetime(dt["datetime"]) + dt["year"] = dt["datetime"].apply(lambda x: x.year) + + subsets=[] + for yr in year_list: + idx = dt[dt["year"] == yr].index + subsets.append(getData(f, lat, lon, height, + "IDW", + power_estimate=False, + inverse_monin_obukhov_length=False, + start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, + saved_dt=dt)) + atmospheric_df = pd.concat(subsets) + atmospheric_df.index = range(len(atmospheric_df)) + + # output = timeseries_to_12_by_24(atmospheric_df) + # + # output_dest = os.path.join(outputs_dir, req_id) + # with open(output_dest, "w") as text_file: + # text_file.write(output) + # return + # except Exception as e: + # output = "The following error has occurred:
" + str(e) + # with open(output_dest, "w") as text_file: + # text_file.write(output) + # return + + output_table = timeseries_to_12_by_24(atmospheric_df) + + output = "Selected location:


" + \ + """ +
+
+ + + + +
+ %s +
+
+
+ """ % output_table + + info = "Source of data: NREL's WTK dataset, covering 2007-2013." + info += "

The shown subset of the model data includes %d timesteps between %s and %s." % \ + (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's + previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. + Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. + """ + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + except Exception as e: + output = "The following error has occurred:
" + str(e) + info = "" + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + +def serve_monthly(req_id, req_args): + output_dest = os.path.join(outputs_dir, req_id) + try: + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + f = connected_hsds_file(req_args, config) + dt = pd.read_csv("wtk-dt.csv") + dt["datetime"] = pd.to_datetime(dt["datetime"]) + dt["year"] = dt["datetime"].apply(lambda x: x.year) + + subsets=[] + for yr in year_list: + idx = dt[dt["year"] == yr].index + subsets.append(getData(f, lat, lon, height, + "IDW", + power_estimate=False, + inverse_monin_obukhov_length=False, + start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, + saved_dt=dt)) + atmospheric_df = pd.concat(subsets) + atmospheric_df.index = range(len(atmospheric_df)) + + plot_monthly_avg(atmospheric_df, \ + title="Location: (%f, %f), %.0fm hub height" % (lat, lon, height),\ + save_to_file='static/saved.png',\ + show_avg_across_years=True, + show_overall_avg=True, + show=False) + #return flask.send_file('saved.png') + + + output = "Selected location:


" + \ + """ +
+
+ + + + +
+
+
+ """ + # Adding info map after output + #output += "

Selected location:
" + + #info = "The shown dataset includes %d timesteps between %s and %s." % \ + # (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + + info = "Source of data: NREL's WTK dataset, covering 2007-2013." + info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ + (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's + previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. + Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. + """ + + # Adding info map inside the info collapsable box doesn't quite work; there is a strage display problem where map shows up partially + #info += "

Selected location:
" + + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + except Exception as e: + output = "The following error has occurred:
" + str(e) + info = "" + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + +def serve_windrose(req_id, req_args): + output_dest = os.path.join(outputs_dir, req_id) + try: + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + f = connected_hsds_file(req_args, config) + dt = pd.read_csv("wtk-dt.csv") + dt["datetime"] = pd.to_datetime(dt["datetime"]) + dt["year"] = dt["datetime"].apply(lambda x: x.year) + + subsets=[] + for yr in year_list: + idx = dt[dt["year"] == yr].index + subsets.append(getData(f, lat, lon, height, + "IDW", + power_estimate=False, + inverse_monin_obukhov_length=False, + start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, + saved_dt=dt)) + atmospheric_df = pd.concat(subsets) + atmospheric_df.index = range(len(atmospheric_df)) + + plot_name = "%s/windrose_%s.png" % (plot_dir, req_id) + plot_windrose(atmospheric_df, save_to_file=plot_name) + + output = "Selected location:


" + \ + """ +
+
+ + + + +
+
+
+ """ % plot_name + + info = "Source of data: NREL's WTK dataset, covering 2007-2013." + info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ + (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's + previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. + Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. + """ + + # Adding info map inside the info collapsable box doesn't quite work; there is a strage display problem where map shows up partially + #info += "

Selected location:
" + + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + except Exception as e: + output = "The following error has occurred:
" + str(e) + info = "" + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + +def serve_ts(req_id, req_args): + output_dest = os.path.join(outputs_dir, req_id) + try: + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + f = connected_hsds_file(req_args, config) + + # Time index can be obtained from f but reading it from a previously saved file is faster + dt = pd.read_csv("wtk-dt.csv") + dt["datetime"] = pd.to_datetime(dt["datetime"]) + dt["year"] = dt["datetime"].apply(lambda x: x.year) + + subsets=[] + for yr in year_list: + idx = dt[dt["year"] == yr].index + subsets.append(getData(f, lat, lon, height, + "IDW", + power_estimate=False, + inverse_monin_obukhov_length=False, + start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, + saved_dt=dt)) + atmospheric_df = pd.concat(subsets) + atmospheric_df.index = range(len(atmospheric_df)) + atmospheric_df = atmospheric_df.round(3) + if "Unnamed: 0" in atmospheric_df.columns: + atmospheric_df.drop(columns=["Unnamed: 0"], inplace=True) + if "year" in atmospheric_df.columns: + # Year is redundant if datetime is there + atmospheric_df.drop(columns=["year"], inplace=True) + + # Saving to file + csv_dest = "%s/ts-%s.csv" % (csv_dir, req_id) + atmospheric_df.to_csv(csv_dest, index=False) + + output = atmospheric_df.to_csv(index=False).replace("\n", "
") + #info = "" + + info = "Source of data: NREL's WTK dataset, covering 2007-2013." + info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ + (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's + previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. + Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. + """ + + #save = "Download: static/raw/ts-%s.csv" % req_id + proposed_fname="%.6f_%.6f_%.1f.csv" % (lat, lon, height) + save = "href=\"%s\" download=\"%s\"" % (csv_dest, proposed_fname) + # Example: href="static/raw/ts-cd5e6247a3b935d7770bb1657df34715.csv" download="39.7430_-105.1470_65.000000.csv" + # it will be added inside the tag + + json_output = {'output': output, "info": info, "save": save} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + except Exception as e: + output = "The following error has occurred:
" + str(e) + info = "" + save = "" + json_output = {'output': output, "info": info, "save": save} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + +def serve_bc(req_id, req_args): + output_dest = os.path.join(outputs_dir, req_id) + try: + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + f = connected_hsds_file(req_args, config) + dt = pd.read_csv("wtk-dt.csv") + dt["datetime"] = pd.to_datetime(dt["datetime"]) + dt["year"] = dt["datetime"].apply(lambda x: x.year) + + subsets=[] + for yr in year_list: + idx = dt[dt["year"] == yr].index + subsets.append(getData(f, lat, lon, height, + "IDW", + power_estimate=False, + inverse_monin_obukhov_length=False, + start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, + saved_dt=dt)) + atmospheric_df = pd.concat(subsets) + atmospheric_df.index = range(len(atmospheric_df)) + + # plot_monthly_avg(atmospheric_df, \ + # title="Location: (%f, %f), %.0fm hub height" % (lat, lon, height),\ + # save_to_file='static/saved.png',\ + # show_avg_across_years=True, + # show_overall_avg=True, + # show=False) + # #return flask.send_file('saved.png') + + # output = """ + #
+ #
+ # + # + # + # + #
+ #
+ #
+ # """ + + # Ordered list of BC data locations; supports running inside ECS container and locally + # Code below will find first existing and will proceed to using it + bc_locs = ["/bc/bc_v4/", "~/OneDrive - NREL/dw-tap-data/bc_development/bc_v4/"] + selected_bc_loc = None + for bc_loc in bc_locs: + d = os.path.expanduser(bc_loc) + if os.path.isdir(d): + selected_bc_loc = d + if not (selected_bc_loc): + output = """ +
+ Unable to locate directory with BC data. Checked locations: %s. +
+ """ % str(bc_locs) + info = "" + else: + # Todo: check to make sure that atmospheric_df is not empty + + output, bc_info = bc_for_point(lon=lon, lat=lat, height=height, \ + model_data=atmospheric_df, \ + bc_dir=selected_bc_loc,\ + plot_dest = 'static/bc.png') # plot_dest="outputs/fig-%s.png" % req_id) + + basic_info = "Source of data:
NREL's WTK dataset, covering 2007-2013." + basic_info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ + (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + basic_info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's + previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. + Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. + """ + + info = basic_info + "

Additionally, bias correction (BC) was applied to the point estimates. Details:

" + bc_info + #info = "The shown dataset includes %d timesteps between %s and %s." % \ + # (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + except Exception as e: + output = "The following error has occurred:
" + str(e) + info = "" + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + +def serve_kwh(req_id, req_args): + output_dest = os.path.join(outputs_dir, req_id) + try: + + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + if 'pc' in req_args: + pc = req_args['pc'] + if not (pc in powercurves.keys()): + pc = powercurve_default + else: + pc = powercurve_default + + f = connected_hsds_file(req_args, config) + + # Time index can be obtained from f but reading it from a previously saved file is faster + dt = pd.read_csv("wtk-dt.csv") + dt["datetime"] = pd.to_datetime(dt["datetime"]) + dt["year"] = dt["datetime"].apply(lambda x: x.year) + + subsets=[] + for yr in year_list: + idx = dt[dt["year"] == yr].index + subsets.append(getData(f, lat, lon, height, + "IDW", + power_estimate=False, + inverse_monin_obukhov_length=False, + start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, + saved_dt=dt)) + atmospheric_df = pd.concat(subsets) + atmospheric_df.index = range(len(atmospheric_df)) + if "Unnamed: 0" in atmospheric_df.columns: + atmospheric_df.drop(columns=["Unnamed: 0"], inplace=True) + atmospheric_df['year'] = atmospheric_df['datetime'].dt.year + atmospheric_df['month'] = atmospheric_df['datetime'].dt.month + atmospheric_df["Month-Year"] = atmospheric_df['month'].astype(str).apply(lambda x: x.zfill(2)) + "-" + atmospheric_df['year'].astype(str) + + # Add power colum + atmospheric_df["kw"] = powercurves[pc].windspeed_to_kw(atmospheric_df, 'ws') + + # Calculate the time difference between consecutive rows + atmospheric_df['interval_hrs'] = atmospheric_df['datetime'].diff().fillna(pd.Timedelta(seconds=0)).dt.components.hours + + # Energy as the power * time product + atmospheric_df["kwh"] = atmospheric_df["kw"] * atmospheric_df['interval_hrs'] + + atmospheric_df_agg = atmospheric_df.groupby("Month-Year").agg(avg_ws=("ws", "mean"), \ + median_ws=("ws", "median"), \ + energy_total=("kwh", "sum")) + atmospheric_df_agg.rename(columns={"avg_ws": "Avg. wind speed, m/s", "median_ws": "Median wind speed, m/s", "energy_total": "Energy produced, kWh"}, \ + inplace=True) + atmospheric_df_agg = atmospheric_df_agg.round(3) + + # Add summary row + atmospheric_df_agg = pd.concat([atmospheric_df_agg,\ + pd.DataFrame([{"Avg. wind speed, m/s": "Overall avg.: %.3f" % (atmospheric_df_agg["Avg. wind speed, m/s"].mean()),\ + "Median wind speed, m/s": "Overall median: %.3f" % (atmospheric_df["ws"].median()),\ + "Energy produced, kWh": "Total: %.3f" % (atmospheric_df_agg["Energy produced, kWh"].sum())}], index=["Summary"])]) + + output_table = atmospheric_df_agg.to_html(classes='energy_table') + + output = "Selected location:


" + \ + """ +
+
+ + + + +
+ %s +
+
+
+ """ % output_table + + #output = str(atmospheric_df.head()).replace("\n", "
") + + info = "Source of data: NREL's WTK dataset, covering 2007-2013." + info += "

The shown subset of the model data includes %d timesteps between %s and %s." % \ + (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's + previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. + Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. + """ + info += "

Selected power curve: %s" % pc + + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + except Exception as e: + output = "The following error has occurred:
" + str(e) + info = "" + json_output = {'output': output, "info": info} + with open(output_dest, 'w') as f: + json.dump(json_output, f) + return + + +@app.route('/output', methods=['GET']) +def get_output(): + """ Check if output file for requested req_id has been cretead and, if so, return its contents as part of a json in the form expected by the js in html page """ + req_args = request.args + if 'req_id' in req_args: + req_id = request.args['req_id'] + output_path = os.path.join(outputs_dir, req_id) + if os.path.exists(output_path): + output = open(output_path, 'r').read() + else: + output = {'output': "", "info": ""} + else: + output = {'output': "", "info": ""} + return output + +@app.route('/infomap', methods=['GET']) +def get_infomap(): + req_args = request.args + + if not ('lat' in req_args): + return "" + if not ('lon' in req_args): + return "" + + try: + lat = float(req_args["lat"]) + lon = float(req_args["lon"]) + #return get_infomap_script(lat, lon) + + # The following should solve the issue "Refused to execite script because "X-Content-Type-Options: nosniff" was given and its Content-Type is not a script MIME type" + return Response(get_infomap_script(lat, lon), mimetype='text/javascript') + except Exception as e: + return "" + +@app.route('/addr_to_latlon', methods=['GET']) +def addr_to_latlon(): + """ Translate a request with an address to a lat/lon""" + req_args = request.args + if 'addr' in req_args: + addr = request.args['addr'] + try: + url = 'https://nominatim.openstreetmap.org/search?q=' + urllib.parse.quote(addr) +'&format=json' + response = requests.get(url).json() + return json.dumps({"latlon": "lat=%s&lon=%s" % (response[0]["lat"], response[0]["lon"])}) + except Exception as e: + # "Error" at the beginning is important; js will be looking for it in the returned string to handle the error cases + latlon = "Error: OpenStreetMap's API was unable to find lat/lon for the given address.
\ + Provide valid address and try again.
Keep in mind that OpenStreetMap may not provide info for addresses with special restrictions.
\ + If unable to use the desired address, try using this interface and choose the location on the map." % URL_prefix + return json.dumps({"latlon": latlon}) + else: + return json.dumps({"latlon": ""}) + + # address = '1840 Alkire Ct, Golden, CO 80401' + # url = 'https://nominatim.openstreetmap.org/search?q=' + urllib.parse.quote(address) +'&format=json' + # response = requests.get(url).json() + # return response[0]["lat"] + " " + response[0]["lon"] + +@app.route('/testing', methods=['GET']) +def testing(): + return render_template("testing.html") + + +@app.route('/by_address', methods=['GET']) +def by_address(): + return render_template("by_address.html") + +@app.route('/on_map', methods=['GET']) +def on_map(): + return render_template("on_map.html") + +# Old version: serving WindWatts at /map +#@app.route('/map', methods=['GET']) +#def map(): +# return render_template("on_map3.html", google_maps_api_key=given_google_maps_api_key) + +@app.route('/', methods=['GET']) +def map(): + return render_template("on_map3.html", google_maps_api_key=given_google_maps_api_key) + +def process_year_input(year_raw): + + if len(year_raw) > 0: + # Remove spaces + year_raw = year_raw.replace(" ","") + try: + year_list_full = [] + # Works for a list of years and also for a single year + year_list = year_raw.split(",") + for el in year_list: + if "-" in el: + # Year range with a dash + year_start, year_end = int(el.split("-")[0]), int(el.split("-")[1]) + for x in range(year_start, year_end + 1): + year_list_full.append(x) + else: + # Single year + year_list_full.append(int(el)) + year_list = year_list_full + + except ValueError: + raise InvalidUsage(("Year needs to be an integer or a comma-separated list of integers.")) + else: + # Use latest year if not specified + year_list = [2013] + + for yr in year_list: + if yr < 2007 or yr > 2013: + raise InvalidUsage("Each selected year needs to be between 2007 and 2013. One of the selected years: %s" % str(yr)) + + # unique and sorted + year_list = sorted(list(set(year_list))) + return year_list + +def feasibility_test(estimate, thresh, result_prefix=True): + if estimate > thresh: + sign = ">" + if result_prefix: + result = "Result: feasible ✔" + else: + result = "feasible ✔" + else: + sign = "<" + if result_prefix: + result = "Result: not feasible ✗" + else: + result = "not feasible ✗" + return sign, result + +def daily_udf_to_summary(udf): + """ Use daily uncertainty to create a summary """ + p50 = udf["percentile_50"].mean() + p25 = udf["percentile_25"].mean() + p75 = udf["percentile_75"].mean() + + p25_percent_diff_from_p50 = (p50 - p25) / p50 * 100 + p75_percent_diff_from_p50 = (p75 - p50) / p50 * 100 + #return p25, p50, p75, p25_percent_diff_from_p50, p75_percent_diff_from_p50 + + summary = """ +

OLD CONTENT BELOW. TO BE UPDATED.



+ + + + + + + + + + + + + + + + + + +
Average daily 25th percentile:Average daily median:Average daily 75th percentile:
%.2f%.2f%.2f
-%.2f%% from avg. daily median%.2f%% from avg. daily median
+ """ % (p25, p50, p75, p25_percent_diff_from_p50, p75_percent_diff_from_p50) + + summary += """ +


+ + + + + + +
This analysis is performed using WTK-LED's daily uncertainty data for 2018 at 40 meters [More info to be added]. +

The identified range, [-%.2f%%, %.2f%%], characterizes model uncertainty, + and the overall statistics like the all-time wind speed average displayed at the top have at least this much uncertainty.
+ """ % (p25_percent_diff_from_p50, p75_percent_diff_from_p50) + return summary + +def ws_to_level(ws, moderate_resource_thresh_ms, high_resource_thresh_ms): + if ws < moderate_resource_thresh_ms: + return "Low" + elif ws >= moderate_resource_thresh_ms and ws < high_resource_thresh_ms: + return "Moderate" + else: + return "High" + +def serve_data_request(data): + try: + id = data["id"] + + # Creates an empty pending file + # with open(os.path.join(pending_dir, id), 'w') as fp: + # pass + + year_list = process_year_input(data["years"]) + #f = connected_hsds_file(req_args, config) # Need for hsds requests (not needed for WTK-LED s3 fetching) + + # Time index can be obtained from f but reading it from a previously saved file is faster + dt = pd.read_csv("wtk-dt.csv") + dt["datetime"] = pd.to_datetime(dt["datetime"]) + dt["year"] = dt["datetime"].apply(lambda x: x.year) + selected_powercurve = powercurves[data["powercurve"]] + + closest_height = float(data["height"]) + ws_column = "windspeed_" + str(int(data["height"])) + "m" + wd_column = ws_column.replace("windspeed", "winddirection") + kw_column = "windspeed_" + str(int(data["height"])) + "m" + "_kw" + + # New code for WTK-LED (WTK is a default handled below this if) + if data["datasource"] == "WTK-LED": + print("Serving data for WTK-LED request") + + output_dest = os.path.join(completed_dir, id) + + # Fetching WTK-LED + #point = closest_grid_point(wtkled_index, data["lat"], data["lon"]) + #df_1224_20years = get_1224_20yrs(point['index'], ws_column, selected_powercurve, relevant_columns_only=True) + #print("obtained df_1224_20years:") + #print(df_1224_20years.head()) + + attempt_lim = 5 + error_str = "" + for attempt in range(attempt_lim): + try: + error_str = "" + print("Fetching WTK-LED data. Attempt: %d of %d" % (attempt, attempt_lim)) + point = closest_grid_point(wtkled_index, data["lat"], data["lon"]) + df_1224_20years = get_1224_20yrs(point['index'], ws_column, selected_powercurve, relevant_columns_only=True) + except Exception as e: + print("Error in fetching WTK-LED data. Will wait a little and try again") + print("Exception: %s" % str(e)) + error_str = "An error occurred during fetching WTK-LED data. If it is an intermittent, network issue, may need to try again later! To help with troubleshooting, here is the error message: %s" % str(e) + time.sleep(0.5) + continue + else: + print("Fetching WTK-LED data was successful") + error_str = "" + break + + if error_str: + with open(output_dest, 'w') as f: + json.dump({"error": error_str}, f) + print("Saved error info to: %s" % output_dest) + return + + yearly_df = df2yearly_avg(df_1224_20years, ws_column, kw_column) + kwh_produced_avg_year = yearly_df["kWh produced"].tolist()[1] # Average year comes after the lowest + monthly_df = df2monthly_avg(df_1224_20years, ws_column, kw_column) + + overall_mean = df_1224_20years[ws_column].mean() + + feasibility_thresh = feasibility_thresh_by_height(float(data["height"])) + moderate_resource_thresh_ms = feasibility_thresh - 1.0 + high_resource_thresh_ms = feasibility_thresh + 1.0 + ws_level = ws_to_level(overall_mean, moderate_resource_thresh_ms, high_resource_thresh_ms) + + #udf = get_uncertainty_dataframe(point['index']) + #udf['doy'] = udf.index # assume the day of year is the index, which would be true once all 12 months of data are present + #uncertainty_summary = daily_udf_to_summary(udf) + + #udf['iqr'] = udf['percentile_75'] - df['percentile_25'] # compute inter quartile range + #udf['uncertainty'] = udf['percentile_95'] - udf['percentile_5'] # compute inter quartile range + #udf + + summary = summary_to_html(float(data["lat"]), float(data["lon"]), closest_height, data["powercurve"], overall_mean, ws_level, kwh_produced_avg_year) + + windrose_plot_name = "%s/%s_windrose.png" % (plot_dir, id) + plot_windrose(df_1224_20years, ws_column, wd_column, save_to_file=windrose_plot_name) + + windresource = windresource_to_html(overall_mean, \ + ws_level, \ + moderate_resource_thresh_ms=feasibility_thresh-1.0,\ + high_resource_thresh_ms=feasibility_thresh+1.0,\ + closest_height=closest_height, \ + windrose_plot_name=windrose_plot_name) + + + + # Rounding + #yearly_df = yearly_df.round(2) + #monthly_df = monthly_df.round(2) + #udf = udf.round(2) + + #data = yearly_avg_closest_heights.to_html(classes="detailed_yearly_table") + energyproduction = energyproduction_to_html(monthly_df, yearly_df) + + #uncertainty_data = udf.to_html(classes="detailed_yearly_table") + + with open(output_dest, 'w') as f: + json.dump({"wtk_led_summary": summary, "wtk_led_windresource": windresource, + "wtk_led_energyproduction": energyproduction}, #, \ + #"uncertainty_summary": uncertainty_summary, \ + #"uncertainty_data": uncertainty_data},\ + f) + print("Saved output to: %s" % output_dest) + + else: # WTK dataset + + subsets=[] + + for yr in year_list: + idx = dt[dt["year"] == yr].index + df = getData(f, float(data["lat"]), float(data["lon"]), float(data["height"]), + "IDW", + power_estimate=False, + inverse_monin_obukhov_length=False, + start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, + saved_dt=dt) + print(df.head()) + subsets.append(df) + atmospheric_df = pd.concat(subsets) + atmospheric_df.index = range(len(atmospheric_df)) + atmospheric_df = atmospheric_df.round(3) + if "Unnamed: 0" in atmospheric_df.columns: + atmospheric_df.drop(columns=["Unnamed: 0"], inplace=True) + if "year" in atmospheric_df.columns: + # Year is redundant if datetime is there + atmospheric_df.drop(columns=["year"], inplace=True) + + if data["output_type"] == "raw": + output_dest = os.path.join(completed_dir, id) + with open(output_dest, 'w') as f: + json.dump({"raw": atmospheric_df.to_csv(index=False).replace("\n", "
")}, f) + print("Saved output to: %s" % output_dest) + else: + # Assume data["output_type"] == "report" (only 2 options are available now) + + output_dest = os.path.join(completed_dir, id) + + plot_dest = os.path.join(plot_dir, "monthly-%s.png" % id) + plot_monthly_avg(atmospheric_df, \ + title="Location: (%f, %f), %.0fm hub height" % (float(data["lat"]), float(data["lon"]), float(data["height"])),\ + save_to_file=plot_dest,\ + show_avg_across_years=True, + show_overall_avg=True, + show=False) + #return flask.send_file('saved.png') + + output_monthly = """ +
+
+
+ + + +
+
+
+ """ % plot_dest + + output_12x24_table = timeseries_to_12_by_24(atmospheric_df) + output_12x24 = """ +
+
+ + + + +
+ %s +
+
+
+ """ % output_12x24_table + + plot_dest = os.path.join(plot_dir, "windrose_-%s.png" % id) + plot_windrose(atmospheric_df, save_to_file=plot_dest) + output_windrose = """ +
+ +
+ """ % plot_dest + + if data["powercurve"] in powercurves.keys(): + pc = data["powercurve"] + + atmospheric_df['year'] = atmospheric_df['datetime'].dt.year + atmospheric_df['month'] = atmospheric_df['datetime'].dt.month + atmospheric_df["Month-Year"] = atmospheric_df['month'].astype(str).apply(lambda x: x.zfill(2)) + "-" + atmospheric_df['year'].astype(str) + # Add power colum + atmospheric_df["kw"] = powercurves[pc].windspeed_to_kw(atmospheric_df, 'ws') + # Calculate the time difference between consecutive rows + atmospheric_df['interval_hrs'] = atmospheric_df['datetime'].diff().fillna(pd.Timedelta(seconds=0)).dt.components.hours + + # Energy as the power * time product + atmospheric_df["kwh"] = atmospheric_df["kw"] * atmospheric_df['interval_hrs'] + + atmospheric_df_agg = atmospheric_df.groupby("Month-Year").agg(avg_ws=("ws", "mean"), \ + median_ws=("ws", "median"), \ + energy_total=("kwh", "sum")) + atmospheric_df_agg.rename(columns={"avg_ws": "Avg. wind speed, m/s", "median_ws": "Median wind speed, m/s", "energy_total": "Energy produced, kWh"}, \ + inplace=True) + atmospheric_df_agg = atmospheric_df_agg.round(3) + + # Add summary row + atmospheric_df_agg = pd.concat([atmospheric_df_agg,\ + pd.DataFrame([{"Avg. wind speed, m/s": "Overall avg.: %.3f" % (atmospheric_df_agg["Avg. wind speed, m/s"].mean()),\ + "Median wind speed, m/s": "Overall median: %.3f" % (atmospheric_df["ws"].median()),\ + "Energy produced, kWh": "Total: %.3f" % (atmospheric_df_agg["Energy produced, kWh"].sum())}], index=["Summary"])]) + + energy_table = atmospheric_df_agg.to_html(classes='energy_table') + + output_energy = """ +
+
%s
+
+ """ % energy_table + else: + output_energy = "" + + info = "Source of data: NREL's WTK dataset, covering 2007-2013." + info += "

The shown subset of the model data includes %d timesteps between %s and %s." % \ + (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) + info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's + previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. + Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. + """ + + with open(output_dest, 'w') as f: + json.dump({"monthly": output_monthly, "12x24": output_12x24, "windrose": output_windrose, "energy": output_energy, "info": info}, f) + + print("Saved output to: %s" % output_dest) + + except Exception as e: + print("error: " + str(e)) + output = "Error: " + str(e) + + +@app.route('/1224', methods=['GET']) +def serve_1224(): + """ Endpoint serving WTK-LED 12x24 data for 20 years. + + Access it at using URLs like: :/1224?lat=39.76004&lon=-105.14058 + """ + + lat, lon = validated_latlon(request) + point = closest_grid_point(wtkled_index, lat, lon) + df_1224_20years = get_1224_20yrs(point['index'], + ws_col_for_estimating_power="nonexistent_column", # This helps avoid power estimation + selected_powercurve=None, # No power estimation + relevant_columns_only=False) # Show all columns/data instead of a subset + return df_1224_20years.to_csv(index=False) + +# API/documentation route +@app.route('/api', methods=['GET']) +def api(): + # Serve documentation produced by apiDoc + return flask.send_file('docs/index.html') + +@app.route('/data_request', methods=['POST', 'GET']) +def data_request(): + if request.method == 'GET': + # Check if output file for requested req_id has been cretead and, + # if so, return its contents as part of a json """ + req_args = request.args + if 'id' in req_args: + id = request.args['id'] + if id == "": + output = {} + else: + output_path = os.path.join(completed_dir, id) + if os.path.exists(output_path): + output = open(output_path, 'r').read() + else: + output = {} + else: + output = {} + return output + + elif request.method == 'POST': + # POST is used when the page requests new data + data = json.loads(request.data) + serve_data_request(data) + return data + + +@app.route('/energy', methods=['GET']) +def energy(): + return render_template("energy.html") + +@app.route('/status', methods=['GET']) +def status(): + """ Minimal simple status with env info and check for hsds """ + output = "Server started at: " + str(server_started_at) + "
" + output += "Running in AWS? " + str(running_in_aws) + "
" + output += "Host: " + str(host) + "
" + output += "Port: " + str(port) + "
" + output += "URL_prefix: " + URL_prefix + "
" + output += "Google Maps API key: %s
" % ("provided" if given_google_maps_api_key else "not provided") + req_args = request.args + try: + f = connected_hsds_file(req_args, config) + output += "Successfully connected to HSDS.
" + except Exception as e: + output += "Can't connect to HSDS. Error:
" + str(e) + + output += "WTK-LED index: %d columns, %d rows" % (len(wtkled_index.columns), len(wtkled_index)) + return output + +#@app.route('/', methods=['GET']) +#def serve_slash(): +# return render_template("info.html") + +#@app.route('/', methods=['GET']) +@app.route('/') +def root(path): + """ Main routine that servies multiple endpoints """ + current_time = datetime.datetime.utcnow() + + req_args = request.args + + # Simply using request.endpoint doesn't work; it is set to root (name of this func) + req_endpoint = request.base_url.split("/")[-1] + + # Prepare request info for hashing + # For request http://localhost:5000/testing?b=3&a=5 + # string to be hashed is: "2024-02-01 23:05:26.905643testinga5b3" + hashing_src = str(current_time) + hashing_src += req_endpoint + for k in sorted(req_args.keys()): + hashing_src += str(k)+str(req_args[k]) + + # Hash becomes the request ID + req_id = hashlib.md5(hashing_src.encode()).hexdigest() # example: 5ac99a3648385a2230ade76b3f92df0c (unique ID for a request) + + if req_endpoint == "12x24": + th = Thread(target=serve_12x24, args=(req_id, req_args)) + th.start() + + html_name = "12x24_%s.html" % req_id + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + + # Copy from a template and replace the string that has the endpoint for fetching outputs from + # instantiate_from_template(os.path.join(templates_dir, "12x24_index.html"),\ + # os.path.join(templates_dir, "served", html_name), + # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) + instantiate_from_template(os.path.join(templates_dir, "12x24_index.html"),\ + os.path.join(templates_dir, "served", html_name),\ + [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";", \ + "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ + ("const lat = \"NEED_SPECIFIC_LAT\";",\ + "const lat = %.6f;" % lat),\ + ("const lon = \"NEED_SPECIFIC_LON\";",\ + "const lon = %.6f;" % lon)]) + + return render_template(os.path.join("served", html_name)) + + elif req_endpoint == "monthly": + + # Step 1: spin up a separate thread for processing + th = Thread(target=serve_monthly, args=(req_id, req_args)) + th.start() + + # Step 2: from monthly_index.html create html inside served/ for specic request, with specifif lat & lon + # Universal here means that the template can be used for AWS and non-AWS envs + html_name = "monthly_%s.html" % req_id + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + + # Copy from a template and replace the string that has the endpoint for fetching outputs from + # instantiate_from_template(os.path.join(templates_dir, "monthly_index.html"),\ + # os.path.join(templates_dir, "served", html_name), + # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) + instantiate_from_template(os.path.join(templates_dir, "monthly_index.html"),\ + os.path.join(templates_dir, "served", html_name),\ + [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ + ("const lat = \"NEED_SPECIFIC_LAT\";",\ + "const lat = %.6f;" % lat),\ + ("const lon = \"NEED_SPECIFIC_LON\";",\ + "const lon = %.6f;" % lon)]) + + # Step 3: Render the final, filled out template + return render_template(os.path.join("served", html_name)) + + elif req_endpoint == "windrose": + + # Step 1: spin up a separate thread for processing + th = Thread(target=serve_windrose, args=(req_id, req_args)) + th.start() + + # Step 2: from monthly_index.html create html inside served/ for specic request, with specifif lat & lon + # Universal here means that the template can be used for AWS and non-AWS envs + html_name = "windrose_%s.html" % req_id + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + + # Copy from a template and replace the string that has the endpoint for fetching outputs from + # instantiate_from_template(os.path.join(templates_dir, "monthly_index.html"),\ + # os.path.join(templates_dir, "served", html_name), + # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) + instantiate_from_template(os.path.join(templates_dir, "windrose_index.html"),\ + os.path.join(templates_dir, "served", html_name),\ + [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ + ("const lat = \"NEED_SPECIFIC_LAT\";",\ + "const lat = %.6f;" % lat),\ + ("const lon = \"NEED_SPECIFIC_LON\";",\ + "const lon = %.6f;" % lon)]) + + # Step 3: Render the final, filled out template + return render_template(os.path.join("served", html_name)) + + elif req_endpoint == "ts": + th = Thread(target=serve_ts, args=(req_id, req_args)) + th.start() + + html_name = "ts_%s.html" % req_id + + # Copy from a template and replace the string that has the endpoint for fetching outputs from + # instantiate_from_template(os.path.join(templates_dir, "ts_index.html"),\ + # os.path.join(templates_dir, "served", html_name), + # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) + instantiate_from_template(os.path.join(templates_dir, "ts_index.html"),\ + os.path.join(templates_dir, "served", html_name),\ + [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + "const FETCH_STR = \"/output?req_id=%s\";" % req_id)]) + + return render_template(os.path.join("served", html_name)) + + elif req_endpoint == "bc": + th = Thread(target=serve_bc, args=(req_id, req_args)) + th.start() + + html_name = "bc_%s.html" % req_id + + # Copy from a template and replace the string that has the endpoint for fetching outputs from + # instantiate_from_template(os.path.join(templates_dir, "bc_index.html"),\ + # os.path.join(templates_dir, "served", html_name), + # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) + instantiate_from_template(os.path.join(templates_dir, "bc_index.html"),\ + os.path.join(templates_dir, "served", html_name),\ + [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + "const FETCH_STR = \"/output?req_id=%s\";" % req_id)]) + + return render_template(os.path.join("served", html_name)) + + elif req_endpoint == "kwh": + th = Thread(target=serve_kwh, args=(req_id, req_args)) + th.start() + + html_name = "kwh_%s.html" % req_id + height, lat, lon, year_list = validated_params_v2_w_year(req_args) + + # Copy from a template and replace the string that has the endpoint for fetching outputs from + # instantiate_from_template(os.path.join(templates_dir, "bc_index.html"),\ + # os.path.join(templates_dir, "served", html_name), + # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) + instantiate_from_template(os.path.join(templates_dir, "kwh_index.html"),\ + os.path.join(templates_dir, "served", html_name),\ + [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ + "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ + ("const lat = \"NEED_SPECIFIC_LAT\";",\ + "const lat = %.6f;" % lat),\ + ("const lon = \"NEED_SPECIFIC_LON\";",\ + "const lon = %.6f;" % lon)]) + + return render_template(os.path.join("served", html_name)) + + elif req_endpoint == "info": + return render_template("info.html") + else: + return "Unsupported endpoint is selected: %s" % req_endpoint + + +if __name__ == '__main__': + app.run(host=host, port=port, debug=True) diff --git a/proto.py b/proto.py index 014901f..e01326c 100644 --- a/proto.py +++ b/proto.py @@ -15,18 +15,206 @@ import requests import urllib import glob +import calendar import matplotlib import matplotlib.pyplot as plt matplotlib.use('agg') from dw_tap.data_fetching import getData +from dw_tap.observations import locate_nearest_obs_sites from v2 import validated_params_v2_w_year +from v2 import validated_params_v2 +from v2 import validated_latlon from hsds_helpers import connected_hsds_file from bc import bc_for_point from infomap import get_infomap_script from windrose import WindroseAxes from powercurve import PowerCurve +from shapely.geometry import Point +import geopandas as gpd +import heapq +import scipy.interpolate +import numpy as np +from html_maker import * +import flask + +# Start of WTK-LED fucntions +# Code below is copied from the uncertainty notebook by Caleb Phillips +# This function fetches the grid point index and returns as an indexed geodataframe +def get_index(): + # For sample: + #base_url = "https://wtk-csv-testing-dav-sandbox.s3.us-west-2.amazonaws.com" + base_url = "https://wtk-led.s3.us-west-2.amazonaws.com" + file_path = f"/location_index.csv.gz" + index = gpd.GeoDataFrame(pd.read_csv(base_url+file_path)) + index['location'] = gpd.GeoSeries(gpd.points_from_xy(index.longitude, index.latitude)) + return index + +# Get index once when the app starts (rather than as part of every request) +wtkled_index = get_index() + +# This function finds the closest grid point to the given lat/lon +# Note that a faster method might use index.location.sindex, the STRTree optimized +# spatial index, but support is not universal and depends on specific versions of Geos and PyGeos: +# https://pygeos.readthedocs.io/en/latest/strtree.html +# Hence, we're doing the more universally supported, but less optimized thing here +def closest_grid_point(index,lat,lon): + p = Point(lon,lat) + r = index.iloc[index.location.distance(p).argmin()].copy() + r['distance_degrees'] = Point(r['longitude'],r['latitude']).distance(p) + return r + +# Turn mohr values into separate month and hour values +def parse_mohr(mohr): + s = str(mohr) + m, h = s[:-2], s[-2:] + return int(m), int(h) + +# Fetch a single year of WTK-LED 12x24 data +def get_1224(idx, year=2020, add_year_col=False): + # For sample: + #base_url = "https://wtk-csv-testing-dav-sandbox.s3.us-west-2.amazonaws.com/1224" + base_url = "https://wtk-led.s3.us-west-2.amazonaws.com/1224" + file_path = f"/year={year}/varset=all/index={idx}/{idx}_{year}_all.csv.gz" + res = pd.read_csv(base_url + file_path) + res["m"], res["h"] = zip(*res["mohr"].apply(parse_mohr)) + if add_year_col: + res["year"] = year + return res + +# Fetch all 20 years of WTK-LED 12x24 data +def get_1224_20yrs(idx, ws_col_for_estimating_power, selected_powercurve, relevant_columns_only=True): + # Example of the dataframe returned by this func (wtih relevant_columns_only=True): + # + # year mohr month h windspeed_40m windspeed_40m_kw winddirection_40m + # 0 2001 101 Jan 1 4.31 10.765848 334.29 + # 1 2001 102 Jan 2 5.07 17.058348 323.40 + # 2 2001 103 Jan 3 5.38 20.721432 302.85 + + df = pd.concat([get_1224(idx, year=yr, add_year_col=True) for yr in range(2001, 2021)]) + + df.rename(columns={"m": "month"}, inplace=True) + # df.month = df.month.apply(lambda x: calendar.month_abbr[x]) -- converting months to Jan, Feb, etc. should happen last to avoid messing up the other of months + + if ws_col_for_estimating_power in df.columns: + df[ws_col_for_estimating_power + "_kw"] = selected_powercurve.windspeed_to_kw(df, ws_col_for_estimating_power) + if relevant_columns_only: + wd_col = ws_col_for_estimating_power.replace("speed", "direction") + relevant_columns = ["year", "mohr", "month", "h", ws_col_for_estimating_power, ws_col_for_estimating_power + "_kw", wd_col] + return df[relevant_columns] + else: + return df + +# Process a dataframe with 20 years of WTK-LED 12x24 data, turning it into a dataframe with lowest, average, highest years +def df2yearly_avg(df, ws_column, kw_column): + tmp = df.drop(columns=["mohr", "month", "h"] + [x for x in df.columns if "winddirection" in x]) + #res = tmp.groupby("year").agg("mean") + res = tmp.groupby("year").agg(avg_ws=(ws_column, "mean"), kwh_total=(kw_column, "sum")) # kwh_total will sum for all months and all hours + + res["kwh_total"] = res["kwh_total"] * 30 # Coarse estimation: 30 days in every month; no need to /20.0 for individual years + res.rename(columns={"avg_ws": "Average wind speed, m/s", "kwh_total": "kWh produced"}, inplace=True) + + res = res.sort_values("Average wind speed, m/s") + + res_avg = pd.DataFrame(res.mean()).T + res_avg.index=["Average year"] + + res_3years = pd.concat([res.iloc[[0]], res_avg, res.iloc[[-1]]]) + res_3years["kWh produced"] = res_3years["kWh produced"].astype(float).map('{:,.0f}'.format) + res_3years["Average wind speed, m/s"] = res_3years["Average wind speed, m/s"].astype(float).map('{:,.2f}'.format) + + res_3years.index = ["Lowest year (%s)" % str(res_3years.index[0]), \ + res_3years.index[1], \ + "Highest year (%s)" % str(res_3years.index[2])] + + return res_3years + +# Process a dataframe with 20 years of WTK-LED 12x24 data, turning it into a dataframe with monthly averages +def df2monthly_avg(df, ws_column, kw_column): + tmp = df.drop(columns=["year", "mohr", "h"] + [x for x in df.columns if "winddirection" in x]) + res = tmp.groupby("month").agg(avg_ws=(ws_column, "mean"), kwh_total=(kw_column, "sum")) # kwh_total will sum for all hours and all years + + res["kwh_total"] = res["kwh_total"] * 30 / 20.0 # Coarse estimation: 30 days in every month & 20 years + res.rename(columns={"avg_ws": "Average wind speed, m/s", "kwh_total": "kWh produced"}, inplace=True) + res.index = pd.Series(res.index).apply(lambda x: calendar.month_abbr[x]) + + res["kWh produced"] = res["kWh produced"].astype(float).map('{:,.0f}'.format) + res["Average wind speed, m/s"] = res["Average wind speed, m/s"].astype(float).map('{:,.2f}'.format) + + return res + +# Given a 20-year dataframe with columns for different heights and a user-specified height, return closest heights and corresponding column names +def yearly_avg_df_to_closest_heights(yearly_avg, selected_height, heights_count=1): + # Use columns that are of format: windspeed_XYZm where XYZ are integers + all_cols = yearly_avg.columns + heights = [int(c.split("_")[1].rstrip("m")) for c in yearly_avg.columns if "windspeed" in c] + #return heights + + closest_heights = heapq.nsmallest(heights_count, heights, key=lambda x: abs(x-selected_height)) + + closest_heights_columns = [] + for h in closest_heights: + c = "windspeed_%dm" % h + if c in all_cols: + closest_heights_columns.append(c) + # Leave out winddirection columns for now + # c = "winddirection_%dm" % h + # if c in all_cols: + # closest_heights_columns.append(c) + + return closest_heights, closest_heights_columns + +# Function for fetching WTK-LED uncertainty data +def get_uncertainty_dataframe(location,height=40): + # For sample: + #base_url = "https://wtk-csv-testing-dav-sandbox.s3.us-west-2.amazonaws.com/uncertainty" + base_url = "https://wtk-led.s3.us-west-2.amazonaws.com/uncertainty" + file_path = f"/height={height}/index={location}/{location}_{height}m.csv.gz" + return pd.read_csv(base_url+file_path) + +# End of WTK-LED fucntions + +# Determine feasibility threshold given the height +def feasibility_thresh_by_height(h): + """ + From literature (shared by Heidi): + For a small wind turbine hub height of 30 m, 4.0 m/s (9 mph) + is typically cited as the minimum annual average wind speed + required for a feasible project. + For a large wind turbine hub height of 80 m, + a minimum annual average wind speed of 6.5 m/s (14.5 mph) is typically needed. + """ + y_interp = scipy.interpolate.interp1d([30, 80], [4.0, 6.5], fill_value="extrapolate") + return y_interp(h) + +# Helper function for taking an HTML template (src), making a list of replacements (for different environments), svaing it (to dest) +def instantiate_from_template(src, dest, replacements): + """ Copy src file to dest with replacement of old_text with new_text """ + + # This version performs multiple substring replacement, using each tuple provided in the replacements list + # Each element there is: old_text -> new_text + fin = open(src) + fout = open(dest, "wt") + for line in fin: + updated_line = line + for r in replacements: + old_text, new_text = r[0], r[1] + updated_line = updated_line.replace(old_text, new_text) + fout.write(updated_line) + fin.close() + fout.close() + +# Create a wind rose plot and save into a specifed file +def plot_windrose(df, ws_column="ws", wd_column="wd", save_to_file=True): + ax = WindroseAxes.from_ax() + ax.bar(df[wd_column], df[ws_column], normed=True, opening=0.8, edgecolor='white') + ax.set_legend() + if save_to_file == True: + plt.savefig('%s.png' % title, dpi=300) + elif type(save_to_file) == str: + plt.savefig(save_to_file, dpi=300) +# The rest of the server's start-up code server_started_at = datetime.datetime.now() if "GOOGLE_MAPS_API_KEY" in os.environ: @@ -60,6 +248,7 @@ if not os.path.exists(plot_dir): os.mkdir(plot_dir) +# Load and prepare power curve objects put into a dictionary by power curve name powercurves_dir = "powercurves" # Create dict with keys being names of all available power curves (without extensions) # and values being the corresponding PowerCurve objects @@ -73,181 +262,10 @@ if not powercurve_default: powercurve_default = powercurves.keys()[0] -# def instantiate_from_template(src, dest, old_text, new_text): -# """ Copy src file to dest with replacement of old_text with new_text """ -# # This version performs single substring replacement: old_text -> new_text -# -# fin = open(src) -# fout = open(dest, "wt") -# for line in fin: -# fout.write(line.replace(old_text, new_text)) -# fin.close() -# fout.close() - -def instantiate_from_template(src, dest, replacements): - """ Copy src file to dest with replacement of old_text with new_text """ - - # This version performs multiple substring replacement, using each tuple provided in the replacements list - # Each element there is: old_text -> new_text - fin = open(src) - fout = open(dest, "wt") - for line in fin: - updated_line = line - for r in replacements: - old_text, new_text = r[0], r[1] - updated_line = updated_line.replace(old_text, new_text) - fout.write(updated_line) - fin.close() - fout.close() - -def plot_monthly_avg(atmospheric_df, ws_column="ws", datetime_column="datetime", - title="Windspeed monthly averages", - show_avg_across_years=True, - label_avg_across_years=True, - save_to_file=True, - show_overall_avg=True, - show=True): - - if ws_column not in atmospheric_df.columns: - raise ValueError("Can't find %s column in dataframe. Skipping plotting" % ws_column) - if datetime_column not in atmospheric_df.columns: - raise ValueError("Can't find %s column in dataframe. Skipping plotting" % datetime_column) - - df = atmospheric_df[[datetime_column, ws_column]].copy() - - year_month = pd.Series(pd.PeriodIndex(df[datetime_column], freq="M")) - df["month"] = year_month.apply(lambda x: x.month) - df["year"] = year_month.apply(lambda x: x.year) - #display(df) - - fig, ax = plt.subplots(figsize=(10, 3)) - xvals = list(range(1, 13)) # for showing monthly data - - nyears = df["year"].nunique() - if nyears > 1: - for year, grp in df.groupby("year"): - monthly_avg = grp[[ws_column, "month"]].groupby("month").agg("mean") - ax.plot(monthly_avg, label=str(year), linestyle="--", alpha=0.4) - - if show_avg_across_years: - monthly_avg_across_years = df.groupby("month")[ws_column].agg("mean") - ax.plot(monthly_avg_across_years, label="Avg across years (labeled)", marker="o") - if label_avg_across_years: - ylim0 = ax.get_ylim()[0] - ylim1 = ax.get_ylim()[1] - yoffset = ylim1 / 20 # express offest as a fraction of height - yvals = pd.Series(monthly_avg_across_years.tolist()) - a = pd.concat({'x': pd.Series(xvals), - 'y': yvals, - 'val': yvals}, axis=1) - for i, point in a.iterrows(): - t = ax.text(point['x'], point['y'] + yoffset, "%.2f" % point['val'], fontsize=7) - t.set_bbox(dict(facecolor='lightgray', alpha=0.75, edgecolor='red')) - ax.set_ylim([ylim0, ylim1*1.25]) - else: - single_year = df["year"].tolist()[0] - monthly_avg_across_years = df.groupby("month")[ws_column].agg("mean") - ax.plot(monthly_avg_across_years, linestyle="--", label="Year: " + str(single_year), marker="o") - if label_avg_across_years: - ylim0 = ax.get_ylim()[0] - ylim1 = ax.get_ylim()[1] - yoffset = ylim1 / 20 # express offest as a fraction of height - yvals = pd.Series(monthly_avg_across_years.tolist()) - a = pd.concat({'x': pd.Series(xvals), - 'y': yvals, - 'val': yvals}, axis=1) - for i, point in a.iterrows(): - t = ax.text(point['x'], point['y'] + yoffset, "%.2f" % point['val'], fontsize=7) - t.set_bbox(dict(facecolor='lightgray', alpha=0.75, edgecolor='red')) - ax.set_ylim([ylim0, ylim1*1.25]) - - ax.set_xticks(xvals) - ax.set_xticklabels(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]) - - ax.set_ylabel("Monthly avg windspeed, m/s") - ax.set_title(title) - - # plt.figtext(0.1, -0.05, - # "Code used to produce this figure is developed under NREL's TAP project " - # "(https://www.nrel.gov/wind/tools-assessing-performance.html)", - # ha="left", fontsize=6) - - # Shrink current axis by 20% - box = ax.get_position() - ax.set_position([box.x0, box.y0, box.width * 0.8, box.height]) - - # Put a legend to the right of the current axis - ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)); - - if show_overall_avg: - ax.set_xlim([0, 16]) - overall_avg = df[ws_column].mean() - ax.axhline(y=overall_avg,linestyle="dotted", color="orange", linewidth=2.0) - t = ax.text(13.0, overall_avg + yoffset, "Overall avg=%.2f" % overall_avg, fontsize=8) - t.set_bbox(dict(facecolor='orange', alpha=0.3, edgecolor='black')) - - if save_to_file == True: - plt.savefig('%s.png' % title, dpi=300) - elif type(save_to_file) == str: - plt.savefig(save_to_file, dpi=300) - - if show: - plt.show() - -def plot_windrose(df, save_to_file=True): - ax = WindroseAxes.from_ax() - ax.bar(df.wd, df.ws, normed=True, opening=0.8, edgecolor='white') - ax.set_legend() - if save_to_file == True: - plt.savefig('%s.png' % title, dpi=300) - elif type(save_to_file) == str: - plt.savefig(save_to_file, dpi=300) - -def timeseries_to_12_by_24(df, styling=True, format="html"): - res = df - - res["datetime"] = pd.to_datetime(res["datetime"]) - res["hour"] = res["datetime"].apply(lambda x: x.hour) - res["month"] = res["datetime"].apply(lambda x: x.month) - res = res.groupby(["hour", "month"]).agg(ws_avg=("ws", "mean")) - res = res.stack().unstack(level=1) - res.index = ["Hour " + str(el[0]) for el in res.index] # range(len(res)) - res.columns.name = "" - res.rename(columns={c:calendar.month_name[c][:3] for c in res.columns}, inplace=True) - - min_ws = res.min().min() - max_ws = res.max().max() - overall_mean = res.mean().mean() - - if styling: - res = res.style.background_gradient("BuPu", axis=1).format(precision=3).set_caption("Overall average: %.3f" % overall_mean) - if format=="dataframe": - return res - elif format=="html": - #html = res.to_html(classes='12_by_24') - # Add colormap image - #html += "

Min=%.3f

" % (min_ws) - - html = "
%s
" % (res.to_html(table_id='12_by_24')) - - # - # Min=%.3f - # Max=%.3f - # - # - # - # - - # """ % (min_ws, max_ws) - - - return html - else: - return res - with open('config.json', 'r') as f: config = json.load(f) +# Command-line args parsing parser = argparse.ArgumentParser() parser.add_argument('-p', '--production', action='store_true') parser.add_argument('-d', '--development', action='store_true') @@ -267,20 +285,7 @@ def timeseries_to_12_by_24(df, styling=True, format="html"): host = config["production"]["host"] port = config["production"]["port"] -# # Identify the current environment -# # URL_prefix should NOT include "/" at the end, otherwise there will be errors -# if os.environ.get('AWS_EXECUTION_ENV') == "AWS_ECS_EC2": -# running_in_aws = True -# if port == 80 or port == "80": -# URL_prefix = "https://dw-tap.nrel.gov" -# else: -# URL_prefix = "https://dw-tap.nrel.gov:%s" % str(port) -# else: -# running_in_aws = False -# # This case is for running locally (container should be accessed via port 8080 even though inside it the server runs on part 80) -# URL_prefix = "http://localhost:8080" - -# Identify the current environment +# Identify the current environment -- local or AWS # URL_prefix should NOT include "/" at the end, otherwise there will be errors if os.environ.get('ENV') == "prod": running_in_aws = True @@ -306,21 +311,24 @@ def timeseries_to_12_by_24(df, styling=True, format="html"): URL_prefix = "http://localhost:8080" - - -# Now that URL_prefix is determined for the current env, prepare templates from universal ones +# Prepare templates from universal ones (URL_prefix is determined for the current env by now and can be used), # Universal here means that those template can be used for AWS and non-AWS envs -src_dest_names = [("universal_monthly_index.html", "monthly_index.html"),\ - ("universal_windrose_index.html", "windrose_index.html"),\ - ("universal_12x24_index.html", "12x24_index.html"),\ - ("universal_ts_index.html", "ts_index.html"),\ - ("universal_bc_index.html", "bc_index.html"),\ - ("universal_info.html", "info.html"),\ - ("universal_on_map.html", "on_map.html"),\ - ("universal_on_map3.html", "on_map3.html"),\ - ("universal_by_address.html", "by_address.html"),\ - ("universal_kwh_index.html", "kwh_index.html"),\ - ("universal_energy.html", "energy.html")] +# For making permanent changes, need to use unversal_* templates rather than the copies create based on them +# +# src_dest_names = [("universal_monthly_index.html", "monthly_index.html"),\ +# ("universal_windrose_index.html", "windrose_index.html"),\ +# ("universal_12x24_index.html", "12x24_index.html"),\ +# ("universal_ts_index.html", "ts_index.html"),\ +# ("universal_bc_index.html", "bc_index.html"),\ +# ("universal_info.html", "info.html"),\ +# ("universal_on_map.html", "on_map.html"),\ +# ("universal_on_map3.html", "on_map3.html"),\ +# ("universal_by_address.html", "by_address.html"),\ +# ("universal_kwh_index.html", "kwh_index.html"),\ +# ("universal_energy.html", "energy.html")] + +src_dest_names = [("universal_on_map3.html", "on_map3.html")] + for src_dest in src_dest_names: t_src, t_dest = src_dest[0], src_dest[1] t_src = os.path.join(templates_dir, t_src) @@ -339,458 +347,7 @@ def timeseries_to_12_by_24(df, styling=True, format="html"): output = "" req_args = {} -def serve_12x24(req_id, req_args): - output_dest = os.path.join(outputs_dir, req_id) - try: - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - f = connected_hsds_file(req_args, config) - - # Time index can be obtained from f but reading it from a previously saved file is faster - dt = pd.read_csv("wtk-dt.csv") - dt["datetime"] = pd.to_datetime(dt["datetime"]) - dt["year"] = dt["datetime"].apply(lambda x: x.year) - - subsets=[] - for yr in year_list: - idx = dt[dt["year"] == yr].index - subsets.append(getData(f, lat, lon, height, - "IDW", - power_estimate=False, - inverse_monin_obukhov_length=False, - start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, - saved_dt=dt)) - atmospheric_df = pd.concat(subsets) - atmospheric_df.index = range(len(atmospheric_df)) - - # output = timeseries_to_12_by_24(atmospheric_df) - # - # output_dest = os.path.join(outputs_dir, req_id) - # with open(output_dest, "w") as text_file: - # text_file.write(output) - # return - # except Exception as e: - # output = "The following error has occurred:
" + str(e) - # with open(output_dest, "w") as text_file: - # text_file.write(output) - # return - - output_table = timeseries_to_12_by_24(atmospheric_df) - - output = "Selected location:


" + \ - """ -
-
- - - - -
- %s -
-
-
- """ % output_table - - info = "Source of data: NREL's WTK dataset, covering 2007-2013." - info += "

The shown subset of the model data includes %d timesteps between %s and %s." % \ - (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's - previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. - Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. - """ - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - except Exception as e: - output = "The following error has occurred:
" + str(e) - info = "" - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - -def serve_monthly(req_id, req_args): - output_dest = os.path.join(outputs_dir, req_id) - try: - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - f = connected_hsds_file(req_args, config) - dt = pd.read_csv("wtk-dt.csv") - dt["datetime"] = pd.to_datetime(dt["datetime"]) - dt["year"] = dt["datetime"].apply(lambda x: x.year) - - subsets=[] - for yr in year_list: - idx = dt[dt["year"] == yr].index - subsets.append(getData(f, lat, lon, height, - "IDW", - power_estimate=False, - inverse_monin_obukhov_length=False, - start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, - saved_dt=dt)) - atmospheric_df = pd.concat(subsets) - atmospheric_df.index = range(len(atmospheric_df)) - - plot_monthly_avg(atmospheric_df, \ - title="Location: (%f, %f), %.0fm hub height" % (lat, lon, height),\ - save_to_file='static/saved.png',\ - show_avg_across_years=True, - show_overall_avg=True, - show=False) - #return flask.send_file('saved.png') - - - output = "Selected location:


" + \ - """ -
-
- - - - -
-
-
- """ - # Adding info map after output - #output += "

Selected location:
" - - #info = "The shown dataset includes %d timesteps between %s and %s." % \ - # (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - - info = "Source of data: NREL's WTK dataset, covering 2007-2013." - info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ - (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's - previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. - Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. - """ - - # Adding info map inside the info collapsable box doesn't quite work; there is a strage display problem where map shows up partially - #info += "

Selected location:
" - - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - except Exception as e: - output = "The following error has occurred:
" + str(e) - info = "" - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - -def serve_windrose(req_id, req_args): - output_dest = os.path.join(outputs_dir, req_id) - try: - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - f = connected_hsds_file(req_args, config) - dt = pd.read_csv("wtk-dt.csv") - dt["datetime"] = pd.to_datetime(dt["datetime"]) - dt["year"] = dt["datetime"].apply(lambda x: x.year) - - subsets=[] - for yr in year_list: - idx = dt[dt["year"] == yr].index - subsets.append(getData(f, lat, lon, height, - "IDW", - power_estimate=False, - inverse_monin_obukhov_length=False, - start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, - saved_dt=dt)) - atmospheric_df = pd.concat(subsets) - atmospheric_df.index = range(len(atmospheric_df)) - - plot_name = "%s/windrose_%s.png" % (plot_dir, req_id) - plot_windrose(atmospheric_df, save_to_file=plot_name) - - output = "Selected location:


" + \ - """ -
-
- - - - -
-
-
- """ % plot_name - - info = "Source of data: NREL's WTK dataset, covering 2007-2013." - info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ - (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's - previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. - Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. - """ - - # Adding info map inside the info collapsable box doesn't quite work; there is a strage display problem where map shows up partially - #info += "

Selected location:
" - - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - except Exception as e: - output = "The following error has occurred:
" + str(e) - info = "" - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - -def serve_ts(req_id, req_args): - output_dest = os.path.join(outputs_dir, req_id) - try: - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - f = connected_hsds_file(req_args, config) - - # Time index can be obtained from f but reading it from a previously saved file is faster - dt = pd.read_csv("wtk-dt.csv") - dt["datetime"] = pd.to_datetime(dt["datetime"]) - dt["year"] = dt["datetime"].apply(lambda x: x.year) - - subsets=[] - for yr in year_list: - idx = dt[dt["year"] == yr].index - subsets.append(getData(f, lat, lon, height, - "IDW", - power_estimate=False, - inverse_monin_obukhov_length=False, - start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, - saved_dt=dt)) - atmospheric_df = pd.concat(subsets) - atmospheric_df.index = range(len(atmospheric_df)) - atmospheric_df = atmospheric_df.round(3) - if "Unnamed: 0" in atmospheric_df.columns: - atmospheric_df.drop(columns=["Unnamed: 0"], inplace=True) - if "year" in atmospheric_df.columns: - # Year is redundant if datetime is there - atmospheric_df.drop(columns=["year"], inplace=True) - - # Saving to file - csv_dest = "%s/ts-%s.csv" % (csv_dir, req_id) - atmospheric_df.to_csv(csv_dest, index=False) - - output = atmospheric_df.to_csv(index=False).replace("\n", "
") - #info = "" - - info = "Source of data: NREL's WTK dataset, covering 2007-2013." - info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ - (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's - previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. - Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. - """ - - #save = "Download: static/raw/ts-%s.csv" % req_id - proposed_fname="%.6f_%.6f_%.1f.csv" % (lat, lon, height) - save = "href=\"%s\" download=\"%s\"" % (csv_dest, proposed_fname) - # Example: href="static/raw/ts-cd5e6247a3b935d7770bb1657df34715.csv" download="39.7430_-105.1470_65.000000.csv" - # it will be added inside the tag - - json_output = {'output': output, "info": info, "save": save} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - except Exception as e: - output = "The following error has occurred:
" + str(e) - info = "" - save = "" - json_output = {'output': output, "info": info, "save": save} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - -def serve_bc(req_id, req_args): - output_dest = os.path.join(outputs_dir, req_id) - try: - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - f = connected_hsds_file(req_args, config) - dt = pd.read_csv("wtk-dt.csv") - dt["datetime"] = pd.to_datetime(dt["datetime"]) - dt["year"] = dt["datetime"].apply(lambda x: x.year) - - subsets=[] - for yr in year_list: - idx = dt[dt["year"] == yr].index - subsets.append(getData(f, lat, lon, height, - "IDW", - power_estimate=False, - inverse_monin_obukhov_length=False, - start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, - saved_dt=dt)) - atmospheric_df = pd.concat(subsets) - atmospheric_df.index = range(len(atmospheric_df)) - - # plot_monthly_avg(atmospheric_df, \ - # title="Location: (%f, %f), %.0fm hub height" % (lat, lon, height),\ - # save_to_file='static/saved.png',\ - # show_avg_across_years=True, - # show_overall_avg=True, - # show=False) - # #return flask.send_file('saved.png') - - # output = """ - #
- #
- # - # - # - # - #
- #
- #
- # """ - - # Ordered list of BC data locations; supports running inside ECS container and locally - # Code below will find first existing and will proceed to using it - bc_locs = ["/bc/bc_v4/", "~/OneDrive - NREL/dw-tap-data/bc_development/bc_v4/"] - selected_bc_loc = None - for bc_loc in bc_locs: - d = os.path.expanduser(bc_loc) - if os.path.isdir(d): - selected_bc_loc = d - if not (selected_bc_loc): - output = """ -
- Unable to locate directory with BC data. Checked locations: %s. -
- """ % str(bc_locs) - info = "" - else: - # Todo: check to make sure that atmospheric_df is not empty - - output, bc_info = bc_for_point(lon=lon, lat=lat, height=height, \ - model_data=atmospheric_df, \ - bc_dir=selected_bc_loc,\ - plot_dest = 'static/bc.png') # plot_dest="outputs/fig-%s.png" % req_id) - - basic_info = "Source of data:
NREL's WTK dataset, covering 2007-2013." - basic_info += "

The shown subset of model data includes %d timesteps between %s and %s." % \ - (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - basic_info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's - previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. - Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. - """ - - info = basic_info + "

Additionally, bias correction (BC) was applied to the point estimates. Details:

" + bc_info - #info = "The shown dataset includes %d timesteps between %s and %s." % \ - # (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - except Exception as e: - output = "The following error has occurred:
" + str(e) - info = "" - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - -def serve_kwh(req_id, req_args): - output_dest = os.path.join(outputs_dir, req_id) - try: - - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - if 'pc' in req_args: - pc = req_args['pc'] - if not (pc in powercurves.keys()): - pc = powercurve_default - else: - pc = powercurve_default - - f = connected_hsds_file(req_args, config) - - # Time index can be obtained from f but reading it from a previously saved file is faster - dt = pd.read_csv("wtk-dt.csv") - dt["datetime"] = pd.to_datetime(dt["datetime"]) - dt["year"] = dt["datetime"].apply(lambda x: x.year) - - subsets=[] - for yr in year_list: - idx = dt[dt["year"] == yr].index - subsets.append(getData(f, lat, lon, height, - "IDW", - power_estimate=False, - inverse_monin_obukhov_length=False, - start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, - saved_dt=dt)) - atmospheric_df = pd.concat(subsets) - atmospheric_df.index = range(len(atmospheric_df)) - if "Unnamed: 0" in atmospheric_df.columns: - atmospheric_df.drop(columns=["Unnamed: 0"], inplace=True) - atmospheric_df['year'] = atmospheric_df['datetime'].dt.year - atmospheric_df['month'] = atmospheric_df['datetime'].dt.month - atmospheric_df["Month-Year"] = atmospheric_df['month'].astype(str).apply(lambda x: x.zfill(2)) + "-" + atmospheric_df['year'].astype(str) - - # Add power colum - atmospheric_df["kw"] = powercurves[pc].windspeed_to_kw(atmospheric_df, 'ws') - - # Calculate the time difference between consecutive rows - atmospheric_df['interval_hrs'] = atmospheric_df['datetime'].diff().fillna(pd.Timedelta(seconds=0)).dt.components.hours - - # Energy as the power * time product - atmospheric_df["kwh"] = atmospheric_df["kw"] * atmospheric_df['interval_hrs'] - - atmospheric_df_agg = atmospheric_df.groupby("Month-Year").agg(avg_ws=("ws", "mean"), \ - median_ws=("ws", "median"), \ - energy_total=("kwh", "sum")) - atmospheric_df_agg.rename(columns={"avg_ws": "Avg. wind speed, m/s", "median_ws": "Median wind speed, m/s", "energy_total": "Energy produced, kWh"}, \ - inplace=True) - atmospheric_df_agg = atmospheric_df_agg.round(3) - - # Add summary row - atmospheric_df_agg = pd.concat([atmospheric_df_agg,\ - pd.DataFrame([{"Avg. wind speed, m/s": "Overall avg.: %.3f" % (atmospheric_df_agg["Avg. wind speed, m/s"].mean()),\ - "Median wind speed, m/s": "Overall median: %.3f" % (atmospheric_df["ws"].median()),\ - "Energy produced, kWh": "Total: %.3f" % (atmospheric_df_agg["Energy produced, kWh"].sum())}], index=["Summary"])]) - - output_table = atmospheric_df_agg.to_html(classes='energy_table') - - output = "Selected location:


" + \ - """ -
-
- - - - -
- %s -
-
-
- """ % output_table - - #output = str(atmospheric_df.head()).replace("\n", "
") - - info = "Source of data: NREL's WTK dataset, covering 2007-2013." - info += "

The shown subset of the model data includes %d timesteps between %s and %s." % \ - (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's - previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. - Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. - """ - info += "

Selected power curve: %s" % pc - - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - except Exception as e: - output = "The following error has occurred:
" + str(e) - info = "" - json_output = {'output': output, "info": info} - with open(output_dest, 'w') as f: - json.dump(json_output, f) - return - - +# Needed for checking if output is created and ready; checking by the request ID hash @app.route('/output', methods=['GET']) def get_output(): """ Check if output file for requested req_id has been cretead and, if so, return its contents as part of a json in the form expected by the js in html page """ @@ -806,66 +363,13 @@ def get_output(): output = {'output': "", "info": ""} return output -@app.route('/infomap', methods=['GET']) -def get_infomap(): - req_args = request.args - - if not ('lat' in req_args): - return "" - if not ('lon' in req_args): - return "" - - try: - lat = float(req_args["lat"]) - lon = float(req_args["lon"]) - #return get_infomap_script(lat, lon) - - # The following should solve the issue "Refused to execite script because "X-Content-Type-Options: nosniff" was given and its Content-Type is not a script MIME type" - return Response(get_infomap_script(lat, lon), mimetype='text/javascript') - except Exception as e: - return "" - -@app.route('/addr_to_latlon', methods=['GET']) -def addr_to_latlon(): - """ Translate a request with an address to a lat/lon""" - req_args = request.args - if 'addr' in req_args: - addr = request.args['addr'] - try: - url = 'https://nominatim.openstreetmap.org/search?q=' + urllib.parse.quote(addr) +'&format=json' - response = requests.get(url).json() - return json.dumps({"latlon": "lat=%s&lon=%s" % (response[0]["lat"], response[0]["lon"])}) - except Exception as e: - # "Error" at the beginning is important; js will be looking for it in the returned string to handle the error cases - latlon = "Error: OpenStreetMap's API was unable to find lat/lon for the given address.
\ - Provide valid address and try again.
Keep in mind that OpenStreetMap may not provide info for addresses with special restrictions.
\ - If unable to use the desired address, try using this interface and choose the location on the map." % URL_prefix - return json.dumps({"latlon": latlon}) - else: - return json.dumps({"latlon": ""}) - - # address = '1840 Alkire Ct, Golden, CO 80401' - # url = 'https://nominatim.openstreetmap.org/search?q=' + urllib.parse.quote(address) +'&format=json' - # response = requests.get(url).json() - # return response[0]["lat"] + " " + response[0]["lon"] - -@app.route('/testing', methods=['GET']) -def testing(): - return render_template("testing.html") - - -@app.route('/by_address', methods=['GET']) -def by_address(): - return render_template("by_address.html") - -@app.route('/on_map', methods=['GET']) -def on_map(): - return render_template("on_map.html") - -@app.route('/map', methods=['GET']) +# Old version: serving WindWatts at /map +#@app.route('/map', methods=['GET']) +#def map(): +# return render_template("on_map3.html", google_maps_api_key=given_google_maps_api_key) +@app.route('/', methods=['GET']) def map(): - return render_template("on_map3.html", google_maps_api_key=given_google_maps_api_key) - + return render_template("on_map3.html", google_maps_api_key=given_google_maps_api_key) def process_year_input(year_raw): @@ -901,6 +405,79 @@ def process_year_input(year_raw): year_list = sorted(list(set(year_list))) return year_list +def feasibility_test(estimate, thresh, result_prefix=True): + if estimate > thresh: + sign = ">" + if result_prefix: + result = "Result: feasible ✔" + else: + result = "feasible ✔" + else: + sign = "<" + if result_prefix: + result = "Result: not feasible ✗" + else: + result = "not feasible ✗" + return sign, result + +# Produce summary for uncertainty data +def daily_udf_to_summary(udf): + """ Use daily uncertainty to create a summary """ + p50 = udf["percentile_50"].mean() + p25 = udf["percentile_25"].mean() + p75 = udf["percentile_75"].mean() + + p25_percent_diff_from_p50 = (p50 - p25) / p50 * 100 + p75_percent_diff_from_p50 = (p75 - p50) / p50 * 100 + #return p25, p50, p75, p25_percent_diff_from_p50, p75_percent_diff_from_p50 + + summary = """ +

OLD CONTENT BELOW. TO BE UPDATED.



+ + + + + + + + + + + + + + + + + + +
Average daily 25th percentile:Average daily median:Average daily 75th percentile:
%.2f%.2f%.2f
-%.2f%% from avg. daily median%.2f%% from avg. daily median
+ """ % (p25, p50, p75, p25_percent_diff_from_p50, p75_percent_diff_from_p50) + + summary += """ +


+ + + + + + +
This analysis is performed using WTK-LED's daily uncertainty data for 2018 at 40 meters [More info to be added]. +

The identified range, [-%.2f%%, %.2f%%], characterizes model uncertainty, + and the overall statistics like the all-time wind speed average displayed at the top have at least this much uncertainty.
+ """ % (p25_percent_diff_from_p50, p75_percent_diff_from_p50) + return summary + +# Convert wind speed to meaningful levels +def ws_to_level(ws, moderate_resource_thresh_ms, high_resource_thresh_ms): + if ws < moderate_resource_thresh_ms: + return "Low" + elif ws >= moderate_resource_thresh_ms and ws < high_resource_thresh_ms: + return "Moderate" + else: + return "High" + +# Given a request (in the form of a json), produce output file and save it at: (json as well) def serve_data_request(data): try: id = data["id"] @@ -910,105 +487,230 @@ def serve_data_request(data): # pass year_list = process_year_input(data["years"]) - f = connected_hsds_file(req_args, config) + #f = connected_hsds_file(req_args, config) # Need for hsds requests (not needed for WTK-LED s3 fetching) + # Time index can be obtained from f but reading it from a previously saved file is faster dt = pd.read_csv("wtk-dt.csv") dt["datetime"] = pd.to_datetime(dt["datetime"]) dt["year"] = dt["datetime"].apply(lambda x: x.year) + selected_powercurve = powercurves[data["powercurve"]] + + closest_height = float(data["height"]) + ws_column = "windspeed_" + str(int(data["height"])) + "m" + wd_column = ws_column.replace("windspeed", "winddirection") + kw_column = "windspeed_" + str(int(data["height"])) + "m" + "_kw" + + + + output_dest = os.path.join(completed_dir, id) + + # Fetching WTK-LED + #point = closest_grid_point(wtkled_index, data["lat"], data["lon"]) + #df_1224_20years = get_1224_20yrs(point['index'], ws_column, selected_powercurve, relevant_columns_only=True) + #print("obtained df_1224_20years:") + #print(df_1224_20years.head()) + + # ToDo: the following code can eventually be replaces with a simple call to new API built around WTK-LED and other datasets + attempt_lim = 5 + error_str = "" + for attempt in range(attempt_lim): + try: + error_str = "" + print("Fetching WTK-LED data. Attempt: %d of %d" % (attempt, attempt_lim)) + point = closest_grid_point(wtkled_index, data["lat"], data["lon"]) + df_1224_20years = get_1224_20yrs(point['index'], ws_column, selected_powercurve, relevant_columns_only=True) + except Exception as e: + print("Error in fetching WTK-LED data. Will wait a little and try again") + print("Exception: %s" % str(e)) + error_str = "An error occurred during fetching WTK-LED data. If it is an intermittent, network issue, may need to try again later! To help with troubleshooting, here is the error message: %s" % str(e) + time.sleep(0.5) + continue + else: + print("Fetching WTK-LED data was successful") + error_str = "" + break - subsets=[] - - for yr in year_list: - idx = dt[dt["year"] == yr].index - df = getData(f, float(data["lat"]), float(data["lon"]), float(data["height"]), - "IDW", - power_estimate=False, - inverse_monin_obukhov_length=False, - start_time_idx=idx[0], end_time_idx=idx[-1], time_stride=1, - saved_dt=dt) - print(df.head()) - subsets.append(df) - atmospheric_df = pd.concat(subsets) - atmospheric_df.index = range(len(atmospheric_df)) - atmospheric_df = atmospheric_df.round(3) - if "Unnamed: 0" in atmospheric_df.columns: - atmospheric_df.drop(columns=["Unnamed: 0"], inplace=True) - if "year" in atmospheric_df.columns: - # Year is redundant if datetime is there - atmospheric_df.drop(columns=["year"], inplace=True) - - if data["output_type"] == "raw": - output_dest = os.path.join(completed_dir, id) + if error_str: with open(output_dest, 'w') as f: - json.dump({"raw": atmospheric_df.to_csv(index=False).replace("\n", "
")}, f) - print("Saved output to: %s" % output_dest) - else: - # Assume data["output_type"] == "report" (only 2 options are available now) - - output_dest = os.path.join(completed_dir, id) - - plot_dest = os.path.join(plot_dir, "monthly-%s.png" % id) - plot_monthly_avg(atmospheric_df, \ - title="Location: (%f, %f), %.0fm hub height" % (float(data["lat"]), float(data["lon"]), float(data["height"])),\ - save_to_file=plot_dest,\ - show_avg_across_years=True, - show_overall_avg=True, - show=False) - #return flask.send_file('saved.png') - - output_monthly = """ -
-
-
- - - -
-
- - """ % plot_dest - - output_12x24_table = timeseries_to_12_by_24(atmospheric_df) - output_12x24 = """ -
-
- - - - -
- %s -
-
-
- """ % output_12x24_table - - plot_dest = os.path.join(plot_dir, "windrose_-%s.png" % id) - plot_windrose(atmospheric_df, save_to_file=plot_dest) - output_windrose = """ -
- -
- """ % plot_dest - - info = "Source of data: NREL's WTK dataset, covering 2007-2013." - info += "

The shown subset of the model data includes %d timesteps between %s and %s." % \ - (len(atmospheric_df), atmospheric_df.datetime.tolist()[0], atmospheric_df.datetime.tolist()[-1]) - info += """

To get the shown point estimates, TAP API performed horizontal and vertical interpolation based on the TAP team's - previous research published at: https://www.nrel.gov/docs/fy21osti/78412.pdf. - Specifically, the Inverse-Distance Weighting was used for horizontal interpolation and linear interpolation between the two adjacent heights in the model data was used for vertical interpolation. - """ + json.dump({"error": error_str}, f) + print("Saved error info to: %s" % output_dest) + return - with open(output_dest, 'w') as f: - json.dump({"monthly": output_monthly, "12x24": output_12x24, "windrose": output_windrose, "info": info}, f) + yearly_df = df2yearly_avg(df_1224_20years, ws_column, kw_column) + kwh_produced_avg_year = yearly_df["kWh produced"].tolist()[1] # Average year comes after the lowest + monthly_df = df2monthly_avg(df_1224_20years, ws_column, kw_column) + + overall_mean = df_1224_20years[ws_column].mean() + + feasibility_thresh = feasibility_thresh_by_height(float(data["height"])) + moderate_resource_thresh_ms = feasibility_thresh - 1.0 + high_resource_thresh_ms = feasibility_thresh + 1.0 + ws_level = ws_to_level(overall_mean, moderate_resource_thresh_ms, high_resource_thresh_ms) + + #udf = get_uncertainty_dataframe(point['index']) + #udf['doy'] = udf.index # assume the day of year is the index, which would be true once all 12 months of data are present + #uncertainty_summary = daily_udf_to_summary(udf) + + #udf['iqr'] = udf['percentile_75'] - df['percentile_25'] # compute inter quartile range + #udf['uncertainty'] = udf['percentile_95'] - udf['percentile_5'] # compute inter quartile range + #udf + + summary = summary_to_html(float(data["lat"]), float(data["lon"]), closest_height, data["powercurve"], overall_mean, ws_level, kwh_produced_avg_year) + + windrose_plot_name = "%s/%s_windrose.png" % (plot_dir, id) + plot_windrose(df_1224_20years, ws_column, wd_column, save_to_file=windrose_plot_name) + + windresource = windresource_to_html(overall_mean, \ + ws_level, \ + moderate_resource_thresh_ms=feasibility_thresh-1.0,\ + high_resource_thresh_ms=feasibility_thresh+1.0,\ + closest_height=closest_height, \ + windrose_plot_name=windrose_plot_name) + + # Rounding + #yearly_df = yearly_df.round(2) + #monthly_df = monthly_df.round(2) + #udf = udf.round(2) - print("Saved output to: %s" % output_dest) + #data = yearly_avg_closest_heights.to_html(classes="detailed_yearly_table") + energyproduction = energyproduction_to_html(monthly_df, yearly_df) + + #uncertainty_data = udf.to_html(classes="detailed_yearly_table") + + # new addition: summary of observational data + observations = locate_nearest_obs_sites(["./obs/met_tower_obs_summary.geojson", "./obs/vendor_obs_summary.geojson"], \ + float(data["lat"]), float(data["lon"]), float(data["height"])) + + with open(output_dest, 'w') as f: + json.dump({"wtk_led_summary": summary, "wtk_led_windresource": windresource, + "wtk_led_energyproduction": energyproduction, + "observations": observations}, #, \ + #"uncertainty_summary": uncertainty_summary, \ + #"uncertainty_data": uncertainty_data},\ + f) + print("Saved output to: %s" % output_dest) except Exception as e: print("error: " + str(e)) output = "Error: " + str(e) +# API endpoint -- early version +@app.route('/1224', methods=['GET']) +def serve_1224(): + """ Endpoint serving WTK-LED 12x24 data for 20 years. + + Access it at using URLs like: :/1224?lat=39.76004&lon=-105.14058 + """ + + lat, lon = validated_latlon(request) + point = closest_grid_point(wtkled_index, lat, lon) + df_1224_20years = get_1224_20yrs(point['index'], + ws_col_for_estimating_power="nonexistent_column", # This helps avoid power estimation + selected_powercurve=None, # No power estimation + relevant_columns_only=False) # Show all columns/data instead of a subset + return df_1224_20years.to_csv(index=False) + + +def serve_windwatts_api_request_windspeed(req): + + if "lon" in req: + lon = req["lon"] + else: + return error2json("'lon', which is a required field of windwatts-api-request-windspeed, is missing.") + + if "lat" in req: + lat = req["lat"] + else: + return error2json("'lat', which is a required field of windwatts-api-request-windspeed, is missing.") + + if "height" in req: + height = req["height"] + else: + return error2json("'height', which is a required field of windwatts-api-request-windspeed, is missing.") + try: + height = float(height) + except ValueError: + return error2json("Expecting a numeric value provided for 'height'.") + + height_supported_wtkled = [10, 30, 40, 60, 80, 100, 120, 140, 160, 180, 200] + if height in height_supported_wtkled: + # May need to support floats better (10.00, 30.00), etc.; right now only ints will work with this if + pass + else: + return error2json("'height' should match exactly one of WTK-LED heights: %s." % str(height_supported_wtkled)) + + # ToDo: tweak the fetching code to use height + + try: + point = closest_grid_point(wtkled_index, lat, lon) + df_1224_20years = get_1224_20yrs(point['index'], + ws_col_for_estimating_power="nonexistent_column", # This helps avoid power estimation + selected_powercurve=None, # No power estimation + relevant_columns_only=False) # Show all columns/data instead of a subset + + # Debug: + #return json.dumps({"data": str(df_1224_20years.columns)}) + return json.dumps({"data": df_1224_20years.to_csv(index=False),\ + "status": 0}) + + except Exception as e: + return error2json("Error in fetching wind data (WTK-LED 12x24). Error: " + str(e)) + +def serve_windwatts_api_request_windenergy(req): + return "Serving wind energy: " + str(req) + +def error2json(msg, status=1): + """ Always serve error messages as json with 'message' and (non-zero) 'status' """ + return json.dumps({"message": msg,\ + "status": status}) + + + +# API endpoint for batch requests -- early version +@app.route('/batch', methods=['GET']) +def serve_batch(): + """ Endpoint serving multiple requests passed in a single json. + + Access it at using URL: :/batch with attached json via GET method + """ + + if request.method == 'GET': + if not request.data: + return error2json("This endpoint is expecting request data wrapped in a JSON object.") # Maybe add link to a page with API doc + + try: + req_json = json.loads(request.data) + except Exception as e: + return error2json("JSON parsing error. Make sure valid JSON is used. Error: " + str(e)) + + if ("windwatts-api-request-windspeed" not in req_json) and ("windwatts-api-request-windsenergy" not in req_json): + return error2json("Supported types of requests: windwatts-api-request-windspeed, windwatts-api-request-windenergy. Neither was selected in the submitted JSON") + + res = [] + + # Process windspeed requests first, energy requests second (this will allow caching of results of the windspeed requests) + + if 'windwatts-api-request-windspeed' in req_json: + for r in req_json["windwatts-api-request-windspeed"]: + res.append(serve_windwatts_api_request_windspeed(r)) + + if 'windwatts-api-request-windenergy' in req_json: + for r in req_json["windwatts-api-request-windenergy"]: + res.append(serve_windwatts_api_request_windenergy(r)) + + return res + else: + return error2json("Use GET method with this endpoint.") + + +# API/documentation route -- not working now because apiDoc isn't working for buildings docs (deprecated) +@app.route('/api', methods=['GET']) +def api(): + # Serve documentation produced by apiDoc + return flask.send_file('docs/index.html') + +# UI uses this endpoint to make the initial request (with POST) and checking the availability of results (using GET) @app.route('/data_request', methods=['POST', 'GET']) def data_request(): if request.method == 'GET': @@ -1035,11 +737,7 @@ def data_request(): serve_data_request(data) return data - -@app.route('/energy', methods=['GET']) -def energy(): - return render_template("energy.html") - +# Display some server info @app.route('/status', methods=['GET']) def status(): """ Minimal simple status with env info and check for hsds """ @@ -1055,177 +753,14 @@ def status(): output += "Successfully connected to HSDS.
" except Exception as e: output += "Can't connect to HSDS. Error:
" + str(e) - return output - -@app.route('/', methods=['GET']) -def serve_slash(): - return render_template("info.html") -#@app.route('/', methods=['GET']) -@app.route('/') -def root(path): - """ Main routine that servies multiple endpoints """ - current_time = datetime.datetime.utcnow() - - req_args = request.args - - # Simply using request.endpoint doesn't work; it is set to root (name of this func) - req_endpoint = request.base_url.split("/")[-1] - - # Prepare request info for hashing - # For request http://localhost:5000/testing?b=3&a=5 - # string to be hashed is: "2024-02-01 23:05:26.905643testinga5b3" - hashing_src = str(current_time) - hashing_src += req_endpoint - for k in sorted(req_args.keys()): - hashing_src += str(k)+str(req_args[k]) - - # Hash becomes the request ID - req_id = hashlib.md5(hashing_src.encode()).hexdigest() # example: 5ac99a3648385a2230ade76b3f92df0c (unique ID for a request) - - if req_endpoint == "12x24": - th = Thread(target=serve_12x24, args=(req_id, req_args)) - th.start() - - html_name = "12x24_%s.html" % req_id - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - - # Copy from a template and replace the string that has the endpoint for fetching outputs from - # instantiate_from_template(os.path.join(templates_dir, "12x24_index.html"),\ - # os.path.join(templates_dir, "served", html_name), - # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) - instantiate_from_template(os.path.join(templates_dir, "12x24_index.html"),\ - os.path.join(templates_dir, "served", html_name),\ - [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";", \ - "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ - ("const lat = \"NEED_SPECIFIC_LAT\";",\ - "const lat = %.6f;" % lat),\ - ("const lon = \"NEED_SPECIFIC_LON\";",\ - "const lon = %.6f;" % lon)]) - - return render_template(os.path.join("served", html_name)) - - elif req_endpoint == "monthly": - - # Step 1: spin up a separate thread for processing - th = Thread(target=serve_monthly, args=(req_id, req_args)) - th.start() - - # Step 2: from monthly_index.html create html inside served/ for specic request, with specifif lat & lon - # Universal here means that the template can be used for AWS and non-AWS envs - html_name = "monthly_%s.html" % req_id - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - - # Copy from a template and replace the string that has the endpoint for fetching outputs from - # instantiate_from_template(os.path.join(templates_dir, "monthly_index.html"),\ - # os.path.join(templates_dir, "served", html_name), - # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) - instantiate_from_template(os.path.join(templates_dir, "monthly_index.html"),\ - os.path.join(templates_dir, "served", html_name),\ - [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ - ("const lat = \"NEED_SPECIFIC_LAT\";",\ - "const lat = %.6f;" % lat),\ - ("const lon = \"NEED_SPECIFIC_LON\";",\ - "const lon = %.6f;" % lon)]) - - # Step 3: Render the final, filled out template - return render_template(os.path.join("served", html_name)) - - elif req_endpoint == "windrose": - - # Step 1: spin up a separate thread for processing - th = Thread(target=serve_windrose, args=(req_id, req_args)) - th.start() - - # Step 2: from monthly_index.html create html inside served/ for specic request, with specifif lat & lon - # Universal here means that the template can be used for AWS and non-AWS envs - html_name = "windrose_%s.html" % req_id - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - - # Copy from a template and replace the string that has the endpoint for fetching outputs from - # instantiate_from_template(os.path.join(templates_dir, "monthly_index.html"),\ - # os.path.join(templates_dir, "served", html_name), - # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) - instantiate_from_template(os.path.join(templates_dir, "windrose_index.html"),\ - os.path.join(templates_dir, "served", html_name),\ - [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ - ("const lat = \"NEED_SPECIFIC_LAT\";",\ - "const lat = %.6f;" % lat),\ - ("const lon = \"NEED_SPECIFIC_LON\";",\ - "const lon = %.6f;" % lon)]) - - # Step 3: Render the final, filled out template - return render_template(os.path.join("served", html_name)) - - elif req_endpoint == "ts": - th = Thread(target=serve_ts, args=(req_id, req_args)) - th.start() - - html_name = "ts_%s.html" % req_id - - # Copy from a template and replace the string that has the endpoint for fetching outputs from - # instantiate_from_template(os.path.join(templates_dir, "ts_index.html"),\ - # os.path.join(templates_dir, "served", html_name), - # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) - instantiate_from_template(os.path.join(templates_dir, "ts_index.html"),\ - os.path.join(templates_dir, "served", html_name),\ - [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - "const FETCH_STR = \"/output?req_id=%s\";" % req_id)]) - - return render_template(os.path.join("served", html_name)) - - elif req_endpoint == "bc": - th = Thread(target=serve_bc, args=(req_id, req_args)) - th.start() - - html_name = "bc_%s.html" % req_id - - # Copy from a template and replace the string that has the endpoint for fetching outputs from - # instantiate_from_template(os.path.join(templates_dir, "bc_index.html"),\ - # os.path.join(templates_dir, "served", html_name), - # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) - instantiate_from_template(os.path.join(templates_dir, "bc_index.html"),\ - os.path.join(templates_dir, "served", html_name),\ - [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - "const FETCH_STR = \"/output?req_id=%s\";" % req_id)]) - - return render_template(os.path.join("served", html_name)) - - elif req_endpoint == "kwh": - th = Thread(target=serve_kwh, args=(req_id, req_args)) - th.start() - - html_name = "kwh_%s.html" % req_id - height, lat, lon, year_list = validated_params_v2_w_year(req_args) - - # Copy from a template and replace the string that has the endpoint for fetching outputs from - # instantiate_from_template(os.path.join(templates_dir, "bc_index.html"),\ - # os.path.join(templates_dir, "served", html_name), - # old_text="const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - # new_text="const FETCH_STR = \"/output?req_id=%s\";" % req_id) - instantiate_from_template(os.path.join(templates_dir, "kwh_index.html"),\ - os.path.join(templates_dir, "served", html_name),\ - [("const FETCH_STR = \"/output?req_id=NEED_SPECIFIC_REQ_ID\";",\ - "const FETCH_STR = \"/output?req_id=%s\";" % req_id),\ - ("const lat = \"NEED_SPECIFIC_LAT\";",\ - "const lat = %.6f;" % lat),\ - ("const lon = \"NEED_SPECIFIC_LON\";",\ - "const lon = %.6f;" % lon)]) - - return render_template(os.path.join("served", html_name)) - - elif req_endpoint == "info": - return render_template("info.html") - else: - return "Unsupported endpoint is selected: %s" % req_endpoint + output += "WTK-LED index: %d columns, %d rows" % (len(wtkled_index.columns), len(wtkled_index)) + return output +# DO NOT DELETE: /info route is needed for checking the health of the service during deployment +@app.route('/info', methods=['GET']) +def info(): + return status() if __name__ == '__main__': app.run(host=host, port=port, debug=True) diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..be269a8 --- /dev/null +++ b/release.sh @@ -0,0 +1,9 @@ +export PROJECT_HANDLE=tap +export APP_NAME=api +export MAKEFILE_PATH=./awscodebuild/Makefile +export APPFLEET_RELEASE_NAME=dev +export APPFLEET_DEPLOY_VERSION=0.0.12-1.0.0-alpine-3c676c3 +export BASE_IMAGE_TAG=3-alpine + +make -f "$MAKEFILE_PATH" V=1 release + diff --git a/requirements.txt b/requirements.txt index 8ddbbee..1219433 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,10 +12,7 @@ scipy setuptools Shapely tensorflow -xarray -ecmwflibs -cfgrib -eccodes==1.2.0 descartes joblib windrose +tqdm diff --git a/static/NREL-logo-reversed.png b/static/NREL-logo-reversed.png new file mode 100644 index 0000000..21fcb2d Binary files /dev/null and b/static/NREL-logo-reversed.png differ diff --git a/static/styles/banner.css b/static/styles/banner.css index 27d8267..cdee16b 100644 --- a/static/styles/banner.css +++ b/static/styles/banner.css @@ -34,11 +34,16 @@ html { */ .top-banner span{ - width: 50px; - heigth: 28px; + width: 700px; line-height: 28px; - background-color: #a1c45a; - border-radius: 20px; + background-color: #E6F4F1; + border-radius: 3px; + margin-left: 20px; + margin-top: 25px; + position: absolute; + font-family: Verdana; + text-align: center; + font-size: 16px; } .top-banner a:hover { diff --git a/static/styles/map3.css b/static/styles/map3.css index 188b2d0..bcd1c24 100644 --- a/static/styles/map3.css +++ b/static/styles/map3.css @@ -5,10 +5,10 @@ #banner_nrel { width: 200px; - margin-top: 8px; + margin-top: 12px; margin-right: 40px; float: right; - height: 60px; + /*height: 60px; */ } .vspace{ @@ -297,3 +297,249 @@ input[type=text] { font-family: Verdana; font-size: 22px; } + +.powercurve_fieldset { + border-width: 0; + line-height: 32px; + margin-top: 10px; + margin-bottom: 10px; +} + +.hubheight_fieldset { + border-width: 0; + line-height: 32px; + margin-top: 10px; + margin-bottom: 10px; +} + +.hubheight_label { + margin-top: 10px; +} + +.details_label { + font-family: "Lato",sans-serif; + font-size: 28px; + font-weight: 400; + text-decoration: underline; +} + +#windwatts_loading { + height: 60px; + margin-bottom: 0; +} + +.loading_screen_table { + width: 100%; +} + +.production_label { + font-family: "Lato",sans-serif; + font-size: 28px; + font-weight: 400; + margin-bottom: 10px; +} + +.estimates_by_month_table { + border-collapse: collapse; + width: 90%; +} +.estimates_by_month_table td, .estimates_by_month_table th { + border: 0px solid #ddd; + padding: 8px; +} +.estimates_by_month_table tr:nth-child(even){background-color: #f2f2f2;} +.estimates_by_month_table th { + padding-top: 12px; + padding-bottom: 12px; + text-align: center; + background-color: #e4e4e4; + color: black; +} + +.estimates_by_year_table { + border-collapse: collapse; + width: 90%; +} +.estimates_by_year_table td, .estimates_by_year_table th { + border: 0px solid #ddd; + padding: 8px; +} +.estimates_by_year_table tr:nth-child(even){background-color: #f2f2f2;} +.estimates_by_year_table th { + padding-top: 12px; + padding-bottom: 12px; + text-align: center; + background-color: #e4e4e4; + color: black; +} + +.results_text li { + margin-left: 25px; +} + +.tr_top_align { + vertical-align: top; +} + +/* Styling for the hub height slider */ +.options { + width: 90%; + margin-left: 5%; + margin-right: 5%; + margin-top: 10px; + /*width: 30em;*/ + border-radius: 1em; + padding: 0 0 1em 0; + text-align: center; + display: flex; + flex-direction: column; +} + +.ticks { + display: flex; + margin-left: -5%; + margin-right: -5%; +} + +.o_txt { + flex: 1; +} + +.slider { + width: 87%; /* manually adjust this to look right */ + margin: auto; + cursor: grab; + margin-top: 20px; +} + +/* End of Styling for the hub height slider */ + + +/* styling for tool tips */ + +.fa-solid, .fas { +font-weight: 900; +color: gray; +} + +.tooltip { + /*background-color: #ffaffc; + border-radius: 10px; + padding: 10px 15px; + margin: 15px; + text-align: center;*/ + position: relative; +} + +.tooltip::after { + background-color: #e4e4e4; + border-radius: 10px; + width: 350px; + color: black; + display: none; + padding: 10px 15px; + position: absolute; + text-align: center; + z-index: 999; +} + +.tooltip::before { + background-color: #e4e4e4; + content: ' '; + display: none; + position: absolute; + width: 15px; + height: 15px; + z-index: 999; +} + +.tooltip:hover::after { + display: block; +} + +.tooltip:hover::before { + display: block; +} + +.tooltip.monthly_table_tooltip::after { + content: 'Table shown below details the wind speed and energy output for each month based on the average of 20 years at the selected site.'; + top: 0; + left: 50%; + margin-left: 100px; + transform: translate(-50%, calc(-100% - 10px)); +} +.tooltip.monthly_table_tooltip::before { + top: 0; + left: 50%; + transform: translate(-50%, calc(-100% - 5px)) rotate(45deg); +} + +.tooltip.yearly_table_tooltip::after { + content: 'Table shown below gives the total energy output for the lowest and highest of 20 years, as well as the average year.'; + top: 0; + left: 50%; + margin-left: 100px; + transform: translate(-50%, calc(-100% - 10px)); +} +.tooltip.yearly_table_tooltip::before { + top: 0; + left: 50%; + transform: translate(-50%, calc(-100% - 5px)) rotate(45deg); +} + +/* end of styling for tool tips*/ + +.obs_table th, td { + padding: 8px; +} + +.obs_table { + text-align: left; + width: 100%; +} + +/* Table styling example: https://dev.to/dcodeyt/creating-beautiful-html-tables-with-css-428l */ + +.obs_table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + /*font-family: sans-serif;*/ + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); +} +.obs_table thead tr { + background-color: #e4e4e4; /* #BD8565; /*#009879; */ + color: black; + text-align: left; +} +.obs_table th, +.obs_table td { + padding: 12px 15px; +} +.obs_table tbody tr { + border-bottom: 0px solid #dddddd; +} + +.obs_table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} + +.obs_table tbody tr:last-of-type { + border-bottom: 1px solid #e4e4e4; /* #BD8565; /*#009879; */ +} +.obs_table tbody tr.active-row { + font-weight: bold; + color: #e4e4e4; /* #BD8565; /*#009879; */ +} + +.obs_table > tbody > tr > td:nth-of-type(1) { + font-family: "Lato",sans-serif; + font-size: 35px; + font-weight: 300; + letter-spacing: 0px; +} + +.bolden { + font-weight: bold; +} diff --git a/static/styles/wtkled_results.css b/static/styles/wtkled_results.css new file mode 100644 index 0000000..365bdfc --- /dev/null +++ b/static/styles/wtkled_results.css @@ -0,0 +1,761 @@ +.summary_table { + width: 90%; + margin-left: auto; + margin-right: auto; +} +.summary_table_metric { + font-size: x-large; +} +.summary_table_value { + font-size: xxx-large; + font-weight: bold; +} +.summary_table_desc { + font-size: small; +} + + +.yearly_table { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +.yearly_table_metric { + font-size: large; +} +.yearly_table_value { + font-size: x-large; + font-weight: bold; +} + +.contents hr { + margin-top: 20px; + margin-bottom: 20px; +} + +.detailed_yearly_table { + width: 40%; + margin-left: auto; + margin-right: auto; +} + +.results_text { + text-align: left; +} + +.results_disclaimer_text { + text-align: left; +} +.results_disclaimer_text span { + background-color: #E0E0E0; + text-decoration: none; +} + + +/* Start of css for summary table */ + +@import url(https://fonts.googleapis.com/css?family=Lato:400,100,100italic,300,300italic,400italic,700italic,700,900italic,900); +@import url(https://fonts.googleapis.com/css?family=Raleway:400,100,200,300,500,600,700,800,900); +@import url(https://fonts.googleapis.com/css?family=Raleway:400,100,200,300,500,600,700,800,900); +body{background-color:#eee;} + +#generic_price_table{ + background-color: #f0eded; +} + +/*PRICE COLOR CODE START*/ +#generic_price_table .generic_content{ + background-color: #fff; +} + +#generic_price_table .generic_content .generic_head_price{ + background-color: #f6f6f6; +} + +#generic_price_table .generic_content .generic_head_price .generic_head_content .head_bg{ + border-color: #e4e4e4 rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) #e4e4e4; +} + +#generic_price_table .generic_content .generic_head_price .generic_head_content .head span{ + color: #525252; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .price .sign{ + color: #414141; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .price .currency{ + color: #414141; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .price .cent{ + color: #414141; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .month{ + color: #414141; +} + +#generic_price_table .generic_content .generic_feature_list ul li{ + color: #a7a7a7; +} + +#generic_price_table .generic_content .generic_feature_list ul li span{ + color: #414141; +} + +#generic_price_table .generic_content .generic_price_btn a{ + border: 1px solid #2ECC71; + color: #2ECC71; +} + + +#generic_price_table{ + margin: 50px 0 50px 0; + font-family: 'Raleway', sans-serif; +} +.row .table{ + padding: 28px 0; +} + +/*PRICE BODY CODE START*/ + +#generic_price_table .generic_content{ + overflow: hidden; + position: relative; + text-align: center; +} + +#generic_price_table .generic_content .generic_head_price { + margin: 0 0 20px 0; +} + +#generic_price_table .generic_content .generic_head_price .generic_head_content{ + margin: 0 0 50px 0; +} + +#generic_price_table .generic_content .generic_head_price .generic_head_content .head_bg{ + border-style: solid; + border-width: 90px 1411px 23px 399px; + position: absolute; +} + +#generic_price_table .generic_content .generic_head_price .generic_head_content .head{ + padding-top: 40px; + position: relative; + z-index: 1; +} + +#generic_price_table .generic_content .generic_head_price .generic_head_content .head span{ + font-family: "Raleway",sans-serif; + font-size: 28px; + font-weight: 400; + letter-spacing: 2px; + margin: 0; + padding: 0; + text-transform: uppercase; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag{ + padding: 0 0 20px; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .price{ + display: block; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .price .sign{ + display: inline-block; + font-family: "Lato",sans-serif; + font-size: 28px; + font-weight: 400; + vertical-align: middle; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .price .currency{ + font-family: "Lato",sans-serif; + font-size: 60px; + font-weight: 300; + letter-spacing: -2px; + line-height: 60px; + padding: 0; + vertical-align: middle; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .price .cent{ + display: inline-block; + font-family: "Lato",sans-serif; + font-size: 24px; + font-weight: 400; + vertical-align: bottom; +} + +#generic_price_table .generic_content .generic_head_price .generic_price_tag .month{ + font-family: "Lato",sans-serif; + font-size: 18px; + font-weight: 400; + letter-spacing: 3px; + vertical-align: bottom; +} + +#generic_price_table .generic_content .generic_feature_list ul{ + list-style: none; + padding: 0; + margin: 0; +} + +#generic_price_table .generic_content .generic_feature_list ul li{ + font-family: "Lato",sans-serif; + font-size: 18px; + padding: 15px 0; + transition: all 0.3s ease-in-out 0s; +} +#generic_price_table .generic_content .generic_feature_list ul li .fa{ + padding: 0 10px; +} +#generic_price_table .generic_content .generic_price_btn{ + margin: 20px 0 32px; +} + +#generic_price_table .generic_content .generic_price_btn a{ + border-radius: 50px; + -moz-border-radius: 50px; + -ms-border-radius: 50px; + -o-border-radius: 50px; + -webkit-border-radius: 50px; + display: inline-block; + font-family: "Lato",sans-serif; + font-size: 18px; + outline: medium none; + padding: 12px 30px; + text-decoration: none; + text-transform: uppercase; +} + +/* +@media (max-width: 320px) { +} + +@media (max-width: 767px) { + #generic_price_table .generic_content{ + margin-bottom:75px; + } +} +@media (min-width: 768px) and (max-width: 991px) { + #generic_price_table .col-md-3{ + float:left; + width:50%; + } +*/ + + #generic_price_table .col-md-4{ + float:left; + /*width:50%;*/ + width:30%; + margin-right:3.3%; + } + + #generic_price_table .generic_content{ + margin-bottom:75px; + } +} +/* +@media (min-width: 992px) and (max-width: 1199px) { +} +@media (min-width: 1200px) { +} +*/ +#generic_price_table_home{ + font-family: 'Raleway', sans-serif; +} + +.text-center h1, +.text-center h1 a{ + color: #7885CB; + font-size: 30px; + font-weight: 300; + text-decoration: none; +} +.demo-pic{ + margin: 0 auto; +} + +#generic_price_table_home ul{ + margin: 0 auto; + padding: 0; + list-style: none; + display: table; +} +#generic_price_table_home li{ + float: left; +} +#generic_price_table_home li + li{ + margin-left: 10px; + padding-bottom: 10px; +} +#generic_price_table_home li a{ + display: block; + width: 50px; + height: 50px; + font-size: 0px; +} +#generic_price_table_home .blue{ + background: #3498DB; + transition: all 0.3s ease-in-out 0s; +} +#generic_price_table_home .emerald{ + background: #2ECC71; + transition: all 0.3s ease-in-out 0s; +} +#generic_price_table_home .grey{ + background: #7F8C8D; + transition: all 0.3s ease-in-out 0s; +} +#generic_price_table_home .midnight{ + background: #34495E; + transition: all 0.3s ease-in-out 0s; +} +#generic_price_table_home .orange{ + background: #E67E22; + transition: all 0.3s ease-in-out 0s; +} +#generic_price_table_home .purple{ + background: #9B59B6; + transition: all 0.3s ease-in-out 0s; +} +#generic_price_table_home .red{ + background: #E74C3C; + transition:all 0.3s ease-in-out 0s; +} +#generic_price_table_home .turquoise{ + background: #1ABC9C; + transition: all 0.3s ease-in-out 0s; +} + +#generic_price_table_home .divider{ + border-bottom: 1px solid #ddd; + margin-bottom: 20px; + padding: 20px; +} +#generic_price_table_home .divider span{ + width: 100%; + display: table; + height: 2px; + background: #ddd; + margin: 50px auto; + line-height: 2px; +} +#generic_price_table_home .itemname{ + text-align: center; + font-size: 50px ; + padding: 50px 0 20px ; + border-bottom: 1px solid #ddd; + margin-bottom: 40px; + text-decoration: none; + font-weight: 300; +} +#generic_price_table_home .itemnametext{ + text-align: center; + font-size: 20px; + padding-top: 5px; + text-transform: uppercase; + display: inline-block; +} +#generic_price_table_home .footer{ + padding:40px 0; +} + +.price-heading{ + text-align: center; +} +.price-heading h1{ + color: #666; + margin: 0; + padding: 0 0 50px 0; +} +.demo-button { + background-color: #333333; + color: #ffffff; + display: table; + font-size: 20px; + margin-left: auto; + margin-right: auto; + margin-top: 20px; + margin-bottom: 50px; + outline-color: -moz-use-text-color; + outline-style: none; + outline-width: medium ; + padding: 10px; + text-align: center; + text-transform: uppercase; +} +.bottom_btn{ + background-color: #333333; + color: #ffffff; + display: table; + font-size: 28px; + margin: 60px auto 20px; + padding: 10px 25px; + text-align: center; + text-transform: uppercase; +} + +/* End of css for summary table */ + +/* Start of css for windresource table */ + +@import url(https://fonts.googleapis.com/css?family=Lato:400,100,100italic,300,300italic,400italic,700italic,700,900italic,900); +@import url(https://fonts.googleapis.com/css?family=Raleway:400,100,200,300,500,600,700,800,900); +@import url(https://fonts.googleapis.com/css?family=Raleway:400,100,200,300,500,600,700,800,900); +body{background-color:#eee;} + +#windresource_table{ + background-color: #f0eded; + margin-left: 15%; + margin-right: 15%; + width: 70%; + display: inline-block; +} + +/*PRICE COLOR CODE START*/ +#windresource_table .windresource_content{ + background-color: #fff; +} + +#windresource_table .windresource_content .windresource_head_price{ + background-color: #f6f6f6; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_head_content .head_bg{ + border-color: #e4e4e4 rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) #e4e4e4; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_head_content .head span{ + color: #525252; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .price .sign{ + color: #414141; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .price .currency{ + color: #414141; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .price .cent{ + color: #414141; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .month{ + color: #414141; +} + +#windresource_table .windresource_content .windresource_feature_list ul li{ + color: #a7a7a7; +} + +#windresource_table .windresource_content .windresource_feature_list ul li span{ + color: #414141; +} + +#windresource_table .windresource_content .windresource_price_btn a{ + border: 1px solid #2ECC71; + color: #2ECC71; +} + + +#windresource_table{ + /*margin: 50px 0 50px 0;*/ + margin: 0 0 0 0; + font-family: 'Raleway', sans-serif; +} +.row .table{ + padding: 28px 0; +} + +/*PRICE BODY CODE START*/ + +#windresource_table .windresource_content{ + overflow: hidden; + position: relative; + text-align: center; +} + +#windresource_table .windresource_content .windresource_head_price { + margin: 0 0 20px 0; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_head_content{ + margin: 0 0 50px 0; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_head_content .head_bg{ + border-style: solid; + border-width: 90px 1411px 23px 399px; + position: absolute; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_head_content .head{ + padding-top: 40px; + position: relative; + z-index: 1; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_head_content .head span{ + font-family: "Raleway",sans-serif; + font-size: 28px; + font-weight: 400; + letter-spacing: 2px; + margin: 0; + padding: 0; + text-transform: uppercase; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag{ + padding: 0 0 20px; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .price{ + display: block; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .price .sign{ + display: inline-block; + font-family: "Lato",sans-serif; + font-size: 28px; + font-weight: 400; + vertical-align: middle; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .price .currency{ + font-family: "Lato",sans-serif; + font-size: 60px; + font-weight: 300; + letter-spacing: -2px; + line-height: 60px; + padding: 0; + vertical-align: middle; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .price .cent{ + display: inline-block; + font-family: "Lato",sans-serif; + font-size: 24px; + font-weight: 400; + vertical-align: bottom; +} + +#windresource_table .windresource_content .windresource_head_price .windresource_price_tag .month{ + font-family: "Lato",sans-serif; + font-size: 18px; + font-weight: 400; + letter-spacing: 3px; + vertical-align: bottom; +} + +#windresource_table .windresource_content .windresource_feature_list ul{ + list-style: none; + padding: 0; + margin: 0; + + /* Additional styling */ + margin-top: -20px; + background-color: #e4e4e4; +} + +#windresource_table .windresource_content .windresource_feature_list ul li{ + font-family: "Lato",sans-serif; + font-size: 18px; + padding: 15px 0; + transition: all 0.3s ease-in-out 0s; +} +#windresource_table .windresource_content .windresource_feature_list ul li .fa{ + padding: 0 10px; +} +#windresource_table .windresource_content .windresource_price_btn{ + margin: 20px 0 32px; +} + +#windresource_table .windresource_content .windresource_price_btn a{ + border-radius: 50px; + -moz-border-radius: 50px; + -ms-border-radius: 50px; + -o-border-radius: 50px; + -webkit-border-radius: 50px; + display: inline-block; + font-family: "Lato",sans-serif; + font-size: 18px; + outline: medium none; + padding: 12px 30px; + text-decoration: none; + text-transform: uppercase; +} + +/* +@media (max-width: 320px) { +} + +@media (max-width: 767px) { + #generic_price_table .generic_content{ + margin-bottom:75px; + } +} +@media (min-width: 768px) and (max-width: 991px) { + #generic_price_table .col-md-3{ + float:left; + width:50%; + } +*/ + + #windresource_table .windresource-col{ + width:100%; + } + + #windresource_table .windresource_content{ + /*margin-bottom:75px;*/ + margin-bottom:0; + } +} +/* +@media (min-width: 992px) and (max-width: 1199px) { +} +@media (min-width: 1200px) { +} +*/ +#windresource_table_home{ + font-family: 'Raleway', sans-serif; +} + +.text-center h1, +.text-center h1 a{ + color: #7885CB; + font-size: 30px; + font-weight: 300; + text-decoration: none; +} +.demo-pic{ + margin: 0 auto; +} + +#windresource_table_home ul{ + margin: 0 auto; + padding: 0; + list-style: none; + display: table; +} +#windresource_table_home li{ + float: left; +} +#windresource_table_home li + li{ + margin-left: 10px; + padding-bottom: 10px; +} +#windresource_table_home li a{ + display: block; + width: 50px; + height: 50px; + font-size: 0px; +} +#windresource_table_home .blue{ + background: #3498DB; + transition: all 0.3s ease-in-out 0s; +} +#windresource_table_home .emerald{ + background: #2ECC71; + transition: all 0.3s ease-in-out 0s; +} +#windresource_table_home .grey{ + background: #7F8C8D; + transition: all 0.3s ease-in-out 0s; +} +#windresource_table_home .midnight{ + background: #34495E; + transition: all 0.3s ease-in-out 0s; +} +#windresource_table_home .orange{ + background: #E67E22; + transition: all 0.3s ease-in-out 0s; +} +#windresource_table_home .purple{ + background: #9B59B6; + transition: all 0.3s ease-in-out 0s; +} +#windresource_table_home .red{ + background: #E74C3C; + transition:all 0.3s ease-in-out 0s; +} +#windresource_table_home .turquoise{ + background: #1ABC9C; + transition: all 0.3s ease-in-out 0s; +} + +#windresource_table_home .divider{ + border-bottom: 1px solid #ddd; + margin-bottom: 20px; + padding: 20px; +} +#windresource_table_home .divider span{ + width: 100%; + display: table; + height: 2px; + background: #ddd; + margin: 50px auto; + line-height: 2px; +} +#windresource_table_home .itemname{ + text-align: center; + font-size: 50px ; + padding: 50px 0 20px ; + border-bottom: 1px solid #ddd; + margin-bottom: 40px; + text-decoration: none; + font-weight: 300; +} +#windresource_table_home .itemnametext{ + text-align: center; + font-size: 20px; + padding-top: 5px; + text-transform: uppercase; + display: inline-block; +} +#windresource_table_home .footer{ + padding:40px 0; +} + +.price-heading{ + text-align: center; +} +.price-heading h1{ + color: #666; + margin: 0; + padding: 0 0 50px 0; +} +.demo-button { + background-color: #333333; + color: #ffffff; + display: table; + font-size: 20px; + margin-left: auto; + margin-right: auto; + margin-top: 20px; + margin-bottom: 50px; + outline-color: -moz-use-text-color; + outline-style: none; + outline-width: medium ; + padding: 10px; + text-align: center; + text-transform: uppercase; +} +.bottom_btn{ + background-color: #333333; + color: #ffffff; + display: table; + font-size: 28px; + margin: 60px auto 20px; + padding: 10px 25px; + text-align: center; + text-transform: uppercase; +} + +/* End of css for windresource table */ diff --git a/static/windwatts.png b/static/windwatts.png new file mode 100644 index 0000000..ec37494 Binary files /dev/null and b/static/windwatts.png differ diff --git a/static/windwatts_beta_b.png b/static/windwatts_beta_b.png new file mode 100644 index 0000000..197ad1f Binary files /dev/null and b/static/windwatts_beta_b.png differ diff --git a/static/windwatts_beta_w.png b/static/windwatts_beta_w.png new file mode 100644 index 0000000..eb41ecb Binary files /dev/null and b/static/windwatts_beta_w.png differ diff --git a/templates/on_map2.html b/templates/on_map2.html index 985a7f8..ef47f91 100644 --- a/templates/on_map2.html +++ b/templates/on_map2.html @@ -26,7 +26,7 @@ for more information. --> diff --git a/templates/on_map3.html b/templates/on_map3.html index bed3be6..5777424 100644 --- a/templates/on_map3.html +++ b/templates/on_map3.html @@ -4,10 +4,12 @@ TAP API: map + + @@ -28,8 +30,9 @@
- - + + To start, click on the map or navigate to a specific location using "Address". +
@@ -87,22 +90,85 @@
+ + +
+
+ +
+ +
+
+ +
+
+ + + + + + +
+ + +
+ 30 + 40 + 60 + 80 + 100 + 120 + 140 +
+
+
+ +
+
+ + +
+ + +
+ + +
+ + +
+ +
+
+ - + +
+
+ +
+
+
+ + + +
+ + + + + + + +
@@ -199,8 +316,8 @@ document.head.appendChild(script); - - + + diff --git a/templates/universal_on_map3.html b/templates/universal_on_map3.html index bed3be6..5777424 100644 --- a/templates/universal_on_map3.html +++ b/templates/universal_on_map3.html @@ -4,10 +4,12 @@ TAP API: map + + @@ -28,8 +30,9 @@
- - + + To start, click on the map or navigate to a specific location using "Address". +
@@ -87,22 +90,85 @@
+ + +
+
+ +
+ +
+
+ +
+
+ + + + + + +
+ + +
+ 30 + 40 + 60 + 80 + 100 + 120 + 140 +
+
+
+ +
+
+ + +
+ + +
+ + +
+ + +
+ +
+
+ - + +
+
+ +
+
+
+ + + +
+ + + + + + + +
@@ -199,8 +316,8 @@ document.head.appendChild(script); - - + +