PastaStore plot and map utilities

This notebook shows the PastaStore functionality for quickly plotting time series, or plotting metadata or models (time series locations) on a map.

Content

  1. Populate a PastaStore with some data

  2. Maps

    1. Oseries locations

    2. All stresses

    3. Model locations

    4. Model statistics

    5. A single model and its time series

  3. Plots

    1. Plot oseries

    2. Plot stresses

    3. Data availability


[1]:
import pandas as pd
import pastastore as pst
import pastas as ps

from pastastore.datasets import example_pastastore
[2]:
ps.logger.setLevel("ERROR")  # silence Pastas for this notebook
ps.show_versions()
Python version: 3.9.7
NumPy version: 1.21.2
Pandas version: 1.5.2
SciPy version: 1.10.0
Matplotlib version: 3.6.1
Numba version: 0.55.1
LMfit version: 1.0.3
Latexify version: Not Installed
Pastas version: 1.0.0b

Populate a PastaStore with some data

First we create a Connector and a PastaStore object and add some data to it. We’re using the example dataset to show the PastaStores plot and map methods.

[3]:
# get the example pastastore
conn = pst.DictConnector("my_connector")
pstore = example_pastastore(conn)

# remove some example data because it's far away
pstore.del_oseries(["head_nb5", "head_mw"])
pstore.del_stress(["prec_nb5", "evap_nb5", "riv_nb5"])
pstore.del_stress(["prec_mw", "evap_mw", "extraction_2", "extraction_3"])
INFO:hydropandas.io.io_menyanthes:reading menyanthes file /home/david/Github/pastastore/pastastore/../tests/data/MenyanthesTest.men
INFO:hydropandas.io.io_menyanthes:reading oseries -> Obsevation well
INFO:hydropandas.io.io_menyanthes:reading stress -> Evaporation
INFO:hydropandas.io.io_menyanthes:reading stress -> Air Pressure
INFO:hydropandas.io.io_menyanthes:reading stress -> Precipitation
INFO:hydropandas.io.io_menyanthes:reading stress -> Extraction 1
INFO:hydropandas.io.io_menyanthes:reading stress -> Extraction 2
INFO:hydropandas.io.io_menyanthes:reading stress -> Extraction 3
INFO:hydropandas.io.io_menyanthes:reading stress -> Extraction 4

Maps

PastaStore contains a maps attribute that exposes methods for spatially plotting data contained in our database. There are methods for plotting oseries, stress and model locations and there is also a method for plotting a single model and all the time series it contains. The following sections showcase each of these methods. But a map is not a map without some kind of background. The function PastaStore.maps.add_background_map allows you to add a background map to any axes object. The method is powered by contextily and basically allows users to access some of the great functionality provided by that package. Contextily is not a pastastore dependency but is obviously recommended, and necessary if you want to access the background maps. For a list of possible background maps, consult PastaStore.maps._list_contextily_providers() (see below). We’ll be using a few different background map options in the plots below. The default is OpenStreetMap.Mapnik.

Background maps

[4]:
# DataFrame of all contextily map providers
providers_df = pd.DataFrame(pstore.maps._list_contextily_providers()).T
providers_df
/home/david/anaconda3/envs/artesia/lib/python3.9/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.7) or chardet (5.1.0)/charset_normalizer (2.0.0) doesn't match a supported version!
[4]:
url max_zoom html_attribution attribution name bounds variant apikey min_zoom subdomains ... language format size time tilematrixset tms detectRetina apiVersion subscriptionKey timeStamp
OpenStreetMap.Mapnik https://{s}.tile.openstreetmap.org/{z}/{x}/{y}... 19 &copy; <a href="https://www.openstreetmap.org/... (C) OpenStreetMap contributors OpenStreetMap.Mapnik NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
OpenStreetMap.DE https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png 18 &copy; <a href="https://www.openstreetmap.org/... (C) OpenStreetMap contributors OpenStreetMap.DE NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
OpenStreetMap.CH https://tile.osm.ch/switzerland/{z}/{x}/{y}.png 18 &copy; <a href="https://www.openstreetmap.org/... (C) OpenStreetMap contributors OpenStreetMap.CH [[45, 5], [48, 11]] NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
OpenStreetMap.France https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x... 20 &copy; OpenStreetMap France | &copy; <a href="... (C) OpenStreetMap France | (C) OpenStreetMap c... OpenStreetMap.France NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
OpenStreetMap.HOT https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/... 19 &copy; <a href="https://www.openstreetmap.org/... (C) OpenStreetMap contributors, Tiles style by... OpenStreetMap.HOT NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Strava.All https://heatmap-external-a.strava.com/tiles/al... 15 Map tiles by <a href="https://labs.strava.com/... Map tiles by <a href="https://labs.strava.com/... Strava.All NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Strava.Ride https://heatmap-external-a.strava.com/tiles/ri... 15 Map tiles by <a href="https://labs.strava.com/... Map tiles by <a href="https://labs.strava.com/... Strava.Ride NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Strava.Run https://heatmap-external-a.strava.com/tiles/ru... 15 Map tiles by <a href="https://labs.strava.com/... Map tiles by <a href="https://labs.strava.com/... Strava.Run NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Strava.Water https://heatmap-external-a.strava.com/tiles/wa... 15 Map tiles by <a href="https://labs.strava.com/... Map tiles by <a href="https://labs.strava.com/... Strava.Water NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Strava.Winter https://heatmap-external-a.strava.com/tiles/wi... 15 Map tiles by <a href="https://labs.strava.com/... Map tiles by <a href="https://labs.strava.com/... Strava.Winter NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

233 rows × 35 columns

[5]:
# list all
pstore.maps._list_contextily_providers().keys()
[5]:
dict_keys(['OpenStreetMap.Mapnik', 'OpenStreetMap.DE', 'OpenStreetMap.CH', 'OpenStreetMap.France', 'OpenStreetMap.HOT', 'OpenStreetMap.BZH', 'OpenStreetMap.BlackAndWhite', 'OpenSeaMap', 'OPNVKarte', 'OpenTopoMap', 'OpenRailwayMap', 'OpenFireMap', 'SafeCast', 'Stadia.AlidadeSmooth', 'Stadia.AlidadeSmoothDark', 'Stadia.OSMBright', 'Stadia.Outdoors', 'Thunderforest.OpenCycleMap', 'Thunderforest.Transport', 'Thunderforest.TransportDark', 'Thunderforest.SpinalMap', 'Thunderforest.Landscape', 'Thunderforest.Outdoors', 'Thunderforest.Pioneer', 'Thunderforest.MobileAtlas', 'Thunderforest.Neighbourhood', 'CyclOSM', 'Jawg.Streets', 'Jawg.Terrain', 'Jawg.Sunny', 'Jawg.Dark', 'Jawg.Light', 'Jawg.Matrix', 'MapBox', 'MapTiler.Streets', 'MapTiler.Basic', 'MapTiler.Bright', 'MapTiler.Pastel', 'MapTiler.Positron', 'MapTiler.Hybrid', 'MapTiler.Toner', 'MapTiler.Topo', 'MapTiler.Voyager', 'MapTiler.Basic4326', 'MapTiler.Outdoor', 'MapTiler.Topographique', 'MapTiler.Winter', 'MapTiler.Satellite', 'MapTiler.Terrain', 'Stamen.Toner', 'Stamen.TonerBackground', 'Stamen.TonerHybrid', 'Stamen.TonerLines', 'Stamen.TonerLabels', 'Stamen.TonerLite', 'Stamen.Watercolor', 'Stamen.Terrain', 'Stamen.TerrainBackground', 'Stamen.TerrainLabels', 'Stamen.TopOSMRelief', 'Stamen.TopOSMFeatures', 'TomTom.Basic', 'TomTom.Hybrid', 'TomTom.Labels', 'Esri.WorldStreetMap', 'Esri.DeLorme', 'Esri.WorldTopoMap', 'Esri.WorldImagery', 'Esri.WorldTerrain', 'Esri.WorldShadedRelief', 'Esri.WorldPhysical', 'Esri.OceanBasemap', 'Esri.NatGeoWorldMap', 'Esri.WorldGrayCanvas', 'Esri.ArcticOceanBase', 'Esri.ArcticOceanReference', 'Esri.AntarcticBasemap', 'OpenWeatherMap.Clouds', 'OpenWeatherMap.CloudsClassic', 'OpenWeatherMap.Precipitation', 'OpenWeatherMap.PrecipitationClassic', 'OpenWeatherMap.Rain', 'OpenWeatherMap.RainClassic', 'OpenWeatherMap.Pressure', 'OpenWeatherMap.PressureContour', 'OpenWeatherMap.Wind', 'OpenWeatherMap.Temperature', 'OpenWeatherMap.Snow', 'HERE.normalDay', 'HERE.normalDayCustom', 'HERE.normalDayGrey', 'HERE.normalDayMobile', 'HERE.normalDayGreyMobile', 'HERE.normalDayTransit', 'HERE.normalDayTransitMobile', 'HERE.normalDayTraffic', 'HERE.normalNight', 'HERE.normalNightMobile', 'HERE.normalNightGrey', 'HERE.normalNightGreyMobile', 'HERE.normalNightTransit', 'HERE.normalNightTransitMobile', 'HERE.reducedDay', 'HERE.reducedNight', 'HERE.basicMap', 'HERE.mapLabels', 'HERE.trafficFlow', 'HERE.carnavDayGrey', 'HERE.hybridDay', 'HERE.hybridDayMobile', 'HERE.hybridDayTransit', 'HERE.hybridDayGrey', 'HERE.hybridDayTraffic', 'HERE.pedestrianDay', 'HERE.pedestrianNight', 'HERE.satelliteDay', 'HERE.terrainDay', 'HERE.terrainDayMobile', 'HEREv3.normalDay', 'HEREv3.normalDayCustom', 'HEREv3.normalDayGrey', 'HEREv3.normalDayMobile', 'HEREv3.normalDayGreyMobile', 'HEREv3.normalDayTransit', 'HEREv3.normalDayTransitMobile', 'HEREv3.normalNight', 'HEREv3.normalNightMobile', 'HEREv3.normalNightGrey', 'HEREv3.normalNightGreyMobile', 'HEREv3.normalNightTransit', 'HEREv3.normalNightTransitMobile', 'HEREv3.reducedDay', 'HEREv3.reducedNight', 'HEREv3.basicMap', 'HEREv3.mapLabels', 'HEREv3.trafficFlow', 'HEREv3.carnavDayGrey', 'HEREv3.hybridDay', 'HEREv3.hybridDayMobile', 'HEREv3.hybridDayTransit', 'HEREv3.hybridDayGrey', 'HEREv3.pedestrianDay', 'HEREv3.pedestrianNight', 'HEREv3.satelliteDay', 'HEREv3.terrainDay', 'HEREv3.terrainDayMobile', 'FreeMapSK', 'MtbMap', 'CartoDB.Positron', 'CartoDB.PositronNoLabels', 'CartoDB.PositronOnlyLabels', 'CartoDB.DarkMatter', 'CartoDB.DarkMatterNoLabels', 'CartoDB.DarkMatterOnlyLabels', 'CartoDB.Voyager', 'CartoDB.VoyagerNoLabels', 'CartoDB.VoyagerOnlyLabels', 'CartoDB.VoyagerLabelsUnder', 'HikeBike.HikeBike', 'HikeBike.HillShading', 'BasemapAT.basemap', 'BasemapAT.grau', 'BasemapAT.overlay', 'BasemapAT.terrain', 'BasemapAT.surface', 'BasemapAT.highdpi', 'BasemapAT.orthofoto', 'nlmaps.standaard', 'nlmaps.pastel', 'nlmaps.grijs', 'nlmaps.water', 'nlmaps.luchtfoto', 'NASAGIBS.ModisTerraTrueColorCR', 'NASAGIBS.ModisTerraBands367CR', 'NASAGIBS.ViirsEarthAtNight2012', 'NASAGIBS.ModisTerraLSTDay', 'NASAGIBS.ModisTerraSnowCover', 'NASAGIBS.ModisTerraAOD', 'NASAGIBS.ModisTerraChlorophyll', 'NASAGIBS.ModisTerraBands721CR', 'NASAGIBS.ModisAquaTrueColorCR', 'NASAGIBS.ModisAquaBands721CR', 'NASAGIBS.ViirsTrueColorCR', 'NASAGIBS.BlueMarble3413', 'NASAGIBS.BlueMarble3031', 'NASAGIBS.BlueMarble', 'NLS', 'JusticeMap.income', 'JusticeMap.americanIndian', 'JusticeMap.asian', 'JusticeMap.black', 'JusticeMap.hispanic', 'JusticeMap.multi', 'JusticeMap.nonWhite', 'JusticeMap.white', 'JusticeMap.plurality', 'GeoportailFrance.plan', 'GeoportailFrance.parcels', 'GeoportailFrance.orthos', 'OneMapSG.Default', 'OneMapSG.Night', 'OneMapSG.Original', 'OneMapSG.Grey', 'OneMapSG.LandLot', 'USGS.USTopo', 'USGS.USImagery', 'USGS.USImageryTopo', 'WaymarkedTrails.hiking', 'WaymarkedTrails.cycling', 'WaymarkedTrails.mtb', 'WaymarkedTrails.slopes', 'WaymarkedTrails.riding', 'WaymarkedTrails.skating', 'OpenAIP', 'OpenSnowMap.pistes', 'AzureMaps.MicrosoftImagery', 'AzureMaps.MicrosoftBaseDarkGrey', 'AzureMaps.MicrosoftBaseRoad', 'AzureMaps.MicrosoftBaseHybridRoad', 'AzureMaps.MicrosoftTerraMain', 'AzureMaps.MicrosoftWeatherInfraredMain', 'AzureMaps.MicrosoftWeatherRadarMain', 'SwissFederalGeoportal.NationalMapColor', 'SwissFederalGeoportal.NationalMapGrey', 'SwissFederalGeoportal.SWISSIMAGE', 'SwissFederalGeoportal.JourneyThroughTime', 'Gaode.Normal', 'Gaode.Satellite', 'Strava.All', 'Strava.Ride', 'Strava.Run', 'Strava.Water', 'Strava.Winter'])

Oseries locations

[6]:
# plot oseries locations
ax1 = pstore.maps.oseries(labels=True, s=100)
pstore.maps.add_background_map(ax1)
../_images/examples_002_pastastore_plots_and_maps_11_0.png

All stresses

[7]:
pstore.stresses
[7]:
x y kind
name
prec1 165050.000000 424050.000000 prec
prec2 164010.000000 423000.000000 prec
evap1 164500.000000 424000.000000 evap
evap2 164000.000000 423030.000000 evap
well1 164691.000000 423579.000000 well
pressure_mw 123715.757915 397547.780543 pressure
extraction_1 81985.384615 380070.307692 well
extraction_4 87111.000000 374334.000000 well
[8]:
# plot all stresses locations
ax2 = pstore.maps.stresses(names=["prec1", "evap1", "well1", "prec2", "evap2"])
pstore.maps.add_background_map(ax2, map_provider="nlmaps.pastel")
../_images/examples_002_pastastore_plots_and_maps_14_0.png

Model locations

[9]:
# create models to show
for o in pstore.oseries.index:
    ml = pstore.create_model(o, modelname=f"{o}_model", add_recharge=True)
    pstore.add_model(ml, overwrite=True)

# plot model location
ax3 = pstore.maps.models(color="C2", s=250, edgecolor="k")
pstore.maps.add_background_map(ax3, map_provider="nlmaps.standaard")
../_images/examples_002_pastastore_plots_and_maps_16_0.png

Model statistics

[10]:
# plot model evp on map
ax4 = pstore.maps.modelstat("evp", s=250, edgecolors="w", linewidths=2)
pstore.maps.add_background_map(ax4, map_provider="Stamen.Watercolor")
../_images/examples_002_pastastore_plots_and_maps_18_0.png

A single model and its time series

[11]:
# plot one model, oseries and stresses
ax5 = pstore.maps.model("oseries2_model", metadata_source="store", offset=250)
pstore.maps.add_background_map(ax5, map_provider="nlmaps.pastel")
../_images/examples_002_pastastore_plots_and_maps_20_0.png

Plots

A PastaStore also has a .plots attribute that exposes methods for plotting time series or an overview of data availability. The examples below run through the different methods and how they work.

Plot oseries

[12]:
# plot oseries
ax6 = pstore.plots.oseries()
Get oseries: 100%|██████████| 3/3 [00:00<00:00, 11086.27it/s]
../_images/examples_002_pastastore_plots_and_maps_24_1.png

Plot stresses

When plotting stresses you can pass the kind argument to select only stresses of a particular kind. The split keyword argument allows you to plot each stress in a separate axis. Note that if there are more than 20 stresses, split is no longer supported.

Also, you can silence the progressbar by passing progressbar=False.

[13]:
# plot well stresses on separate axes
ax7 = pstore.plots.stresses(kind="prec", split=True, progressbar=False)
../_images/examples_002_pastastore_plots_and_maps_26_0.png

Data availability

Plotting data availability shows time period for which data is available and also the observation timestep. Below are three examples for oseries, all stresses, and on;y the evaporation stresses. The set_yticks keyword determines whether the names of the time series are used as yticks. This generally works fine if the number of time series isn’t too large, but for large datasets, setting it to False is recommended.

[14]:
# plot data availability for oseries
ax8 = pstore.plots.data_availability("oseries", set_yticks=True, figsize=(10, 3))
Get oseries: 100%|██████████| 3/3 [00:00<00:00, 6462.72it/s]
../_images/examples_002_pastastore_plots_and_maps_28_1.png
[15]:
# plot data availability for all stresses
ax9 = pstore.plots.data_availability("stresses", set_yticks=True, figsize=(10, 3))
Get stresses: 100%|██████████| 8/8 [00:00<00:00, 8085.41it/s]
../_images/examples_002_pastastore_plots_and_maps_29_1.png
[16]:
# plot data availability only stresses with kind="well"
ax10 = pstore.plots.data_availability(
    "stresses", kind="evap", set_yticks=True, figsize=(10, 2)
)
Get stresses: 100%|██████████| 2/2 [00:00<00:00, 4217.50it/s]
../_images/examples_002_pastastore_plots_and_maps_30_1.png
[ ]: