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]:
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 | © <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 | © <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 | © <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 | © OpenStreetMap France | © <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 | © <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)
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")
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")
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")
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")
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]
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)
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]
[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]
[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]
[ ]: