Skip to content

Commit

Permalink
Add example for annotating phenology data within Grafana
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Jan 6, 2021
1 parent 513bcc7 commit 2d624da
Show file tree
Hide file tree
Showing 5 changed files with 377 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ Changelog
*********


development
===========
- Add example for annotating phenology data within Grafana


2020-12-27 0.1.2
================
- Adjust documentation
Expand Down
161 changes: 161 additions & 0 deletions examples/phenodata-mellifera/dashboard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
{
"overwrite": true,
"dashboard": {
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"limit": 100,
"name": "Annotations & Alerts",
"showIn": 0,
"type": "dashboard"
},
{
"datasource": "phenodata-mellifera",
"enable": true,
"hide": false,
"iconColor": "rgba(255, 96, 96, 1)",
"limit": 100,
"name": "Flowering Events Müncheberg",
"query": "flowering:type=observations&station=müncheberg",
"showIn": 0,
"tags": [],
"type": "tags"
},
{
"datasource": "phenodata-mellifera",
"enable": true,
"hide": false,
"iconColor": "#5794F2",
"limit": 100,
"name": "Flowering Forecast Müncheberg",
"query": "flowering:type=forecast&station=müncheberg",
"showIn": 0,
"tags": [],
"type": "tags"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 2,
"links": [],
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "phenodata-mellifera",
"fieldConfig": {
"defaults": {
"custom": {},
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.3.6",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"target": "select metric",
"type": "timeserie"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Fruits for Apis mellifera in Müncheberg, Brandenburg, Germany",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": false,
"schemaVersion": 26,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-2y",
"to": "now+1y"
},
"timepicker": {},
"timezone": "",
"title": "Fruits for Apis mellifera",
"uid": "aoBz38xMy"
}
}
19 changes: 19 additions & 0 deletions examples/phenodata-mellifera/datasource.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"access": "proxy",
"basicAuth": false,
"basicAuthPassword": "",
"basicAuthUser": "",
"database": "",
"isDefault": false,
"jsonData": {},
"name": "phenodata-mellifera",
"orgId": 1,
"password": "",
"readOnly": true,
"secureJsonFields": {},
"type": "grafana-simple-json-datasource",
"typeLogoUrl": "",
"url": "http://host.docker.internal:3003",
"user": "",
"withCredentials": false
}
120 changes: 120 additions & 0 deletions examples/phenodata-mellifera/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
Copyright 2020 Andreas Motl <andreas@hiveeyes.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import urllib.parse
from datetime import datetime, timedelta

import pandas as pd
from cachetools import cached, TTLCache
from phenodata.dwd.cdc import DwdCdcClient
from phenodata.dwd.pheno import DwdPhenoDataHumanizer, DwdPhenoData
from phenodata.ftp import FTPSession
from phenodata.util import read_list

from grafana_pandas_datasource import create_app
from grafana_pandas_datasource.registry import data_generators as dg
from grafana_pandas_datasource.service import pandas_component

"""
Demo for grafana-pandas-datasource.
This is a demo program which generates flowering events for
phenology species suitable as fruits for bees (Apis mellifera)
as Grafana annotations.
Setup::
pip install grafana-pandas-datasource phenodata>=0.10.0 cachetools
To query the annotations, use ``<reader_name>:<query_string>``, e.g.
- ``flowering:station=müncheberg``
"""


def define_and_register_data():

# Sample annotation reader.
def get_mellifera_flowering(query_string, ts_range):
query = dict(urllib.parse.parse_qsl(query_string))
series = phenodata_mellifera(
dataset="immediate", years=tuple([2019, 2020, 2021]), phases=tuple([5, 7]),
options=tuple(query.items())
)
return series

# Register data generators.
dg.add_annotation_reader("flowering", get_mellifera_flowering)


@cached(cache=TTLCache(maxsize=65536, ttl=24 * 60 * 60))
def phenodata_mellifera(dataset: str, years: tuple[int], phases: tuple[str], options: tuple[tuple[str, str]]):

options = dict(options)
options["type"] = read_list(options.get("type")) or ["observations"]

cdc_client = DwdCdcClient(ftp=FTPSession())
humanizer = DwdPhenoDataHumanizer(language="german", long_station=False, show_ids=False)
client = DwdPhenoData(cdc=cdc_client, humanizer=humanizer, dataset=dataset)

# Rewrite options.
phenodata_options = {
"partition": "recent",
"year": years,
"species": DwdPhenoData.load_preset("options", "species", "mellifera-de-primary"),
"phase-id": phases,
"station": read_list(options.get("station", [])),
"humanize": True,
}

data_total = []

# Get observations
if "observations" in options["type"]:
data_past = client.get_observations(phenodata_options, humanize=phenodata_options['humanize'])
data_total.append(data_past)

if "forecast" in options["type"]:
next_year = (datetime.today() + timedelta(days=365)).year
data_future = client.get_forecast(phenodata_options, forecast_year=next_year, humanize=phenodata_options['humanize'])
data_total.append(data_future)

data = pd.concat(data_total)

# Create Pandas Series from Dataframe.
index = data.Datum.astype('datetime64')
values = data.Spezies.str.cat(data.Phase, sep=" - ").str.cat(data.Station, sep=" - ")
series = pd.Series(data=values.tolist(), index=index)
series = series.sort_index(ascending=True)

return series


def main():

# Define and register data generators.
define_and_register_data()

# Create Flask application.
app = create_app()

# Register Pandas component.
app.register_blueprint(pandas_component, url_prefix="/")

# Invoke Flask application.
app.run(host='127.0.0.1', port=3003, debug=True)


if __name__ == '__main__':
main()
72 changes: 72 additions & 0 deletions examples/phenodata-mellifera/readme.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
################################
Apis mellifera flowering example
################################
This is a demo program which generates flowering events for
phenology species suitable as fruits for bees (Apis mellifera)
as Grafana annotations.

.. figure:: https://user-images.githubusercontent.com/453543/103260962-fd1b8900-499f-11eb-8459-9ecaa6c55ac7.png

Image: Flowering events for some species around Müncheberg, Brandenburg, Germany.


Setup
=====
::

pip install grafana-pandas-datasource phenodata>=0.10.0 cachetools


Acquire example files
=====================
::

export EXAMPLES_BASEURL=https://raw.githubusercontent.com/panodata/grafana-pandas-datasource/0.1.0/examples

wget ${EXAMPLES_BASEURL}/phenodata-mellifera/demo.py \
--output-document=phenodata-mellifera-demo.py

wget ${EXAMPLES_BASEURL}/phenodata-mellifera/datasource.json \
--output-document=phenodata-mellifera-datasource.json

wget ${EXAMPLES_BASEURL}/phenodata-mellifera/dashboard.json \
--output-document=phenodata-mellifera-dashboard.json


Invoke
======
::

# Run Grafana.
docker run --rm -it \
--publish=3000:3000 --volume="$(pwd)/var/lib/grafana":/var/lib/grafana \
--env='GF_SECURITY_ADMIN_PASSWORD=admin' --env='GF_INSTALL_PLUGINS=grafana-simple-json-datasource' \
grafana/grafana:7.3.6

# Run Grafana Pandas Datasource demo.
python phenodata-mellifera-demo.py


Configure
=========
.. note::

The host where the datasource service is running can be accessed from the
Grafana Docker container using the hostname ``host.docker.internal``.

You can have a quickstart by putting ``examples/phenodata-mellifera/datasource.json``
and ``examples/phenodata-mellifera/dashboard.json`` into Grafana::

# Login to Grafana.
export GRAFANA_URL=http://localhost:3000
http --session=grafana ${GRAFANA_URL} --auth=admin:admin

# Create datasource.
cat phenodata-mellifera-datasource.json | \
http --session=grafana POST ${GRAFANA_URL}/api/datasources

# Create dashboard.
cat phenodata-mellifera-dashboard.json | \
http --session=grafana POST ${GRAFANA_URL}/api/dashboards/db

open ${GRAFANA_URL}

0 comments on commit 2d624da

Please sign in to comment.