Commit d30715a0 authored by Samuël Weber's avatar Samuël Weber
Browse files

final touch

parent 10af4004
Acknowledgments
----------------
This extensive study was only possible thanks to the different research programs and
partnership between laboratories, local air quality agencies and institutions. We would
like to sincerely thanks the dedicated effort of many people, notably in the AASQA, for
collecting the samples as well as the work of many technicians and engineers mostly at
IGE, but also at INERIS and IMT-LD. The authors would like to kindly thank them for all
this work.
### Sampling program and chemical analysis
Samples were notably collected and analyzed in the frame of various programs funded by the
French Ministry of Environment through the CARA program leaded by the National Reference
Laboratory for Air Quality Monitoring (LCSQA/INERIS), through programs managed by ADEME or
Primequal, through program funded by Université Grenoble Alpes, as well as programs in the
frame of actions funded by regional air quality monitoring networks (AASQAs).
### Oxidative Potential measurements
The development of the oxidative potential (OP) analytical development was done at IGE
within the [Air-O-Sol ](http://www.ige-grenoble.fr/-plateau-analytique-airosol-)analytical
plateau. All samples were analyzed at IGE for both the OP<sup>DTT</sup> and
OP<sup>AA</sup> assays. The details of the methodology can be found in Calas et al. (2018)
(<https://doi.org/10.1038/s41598-017-11979-3>) for the DTT assay and Calas et al. (2019)
(<https://doi.org/10.5194/acp-18-7863-2018>) for the AA assay.
### Financial support
Analytical aspects were supported at IGE by the Air-O-Sol plateau thanks to Labex
OSUG@2020 (ANR10 LABX56), and the "Investissements d'avenir" programme (ANR-15-IDEX-02)
for funding analytical instruments. Some parts were also funded at IMT LD by the Labex
CaPPA (ANR-11-LABX-0005-01) and CPER CLIMIBIO projects.
The program CNRS-LEFE funded two programs that allowed analytical developments:
1. LEFE-CHAT (2015) LEFE-PO. Le potentiel oxydant : une caractéristique chimique des PM
atmosphériques utilisable comme proxy de l'impact sanitaire.
2. LEFE-CHAT (2017) MECEA : développement de la mesure de la cellulose atmosphérique.
The programs that allowed the collection and chemical analysis of the
samples were funded by:
1. INERIS pour diverses études en collaboration avec le LCSQA dans le cadre de CAR (2012-2021).
1. Etude de la composition chimique détaillée des PM à Lens, Lyon, Bordeaux et Grenoble
pour étude PMF (2012-2014)
2. Suivi des traceurs de combustion de la biomasse à Grenoble Les Frênes (2007-en cours)
2. Programme Primequal (DECOMBIO) (2013-2017) et DECOMBIO 2 (2018-2020).
3. ADEME CORTEA (2011-2013). PM-DRIVE : Emissions particulaires Directes et Indirectes du
trafic routier
4. ADEME (2015-2017). SOURCES : synthèse sur les Sources des PM en France.
5. ADEME (2016-2019). QAMECS (Politiques publiques, qualité de l'air, impacts sanitaires
et économiques, société).
6. IDEX -- COMUE Grenoble. Cross Disciplinary Project (CDP). MobilAir (2018-2021).
7. ANR JCJC (2019) Get OP -- Stand OP (coord. G Uzu).
8. Atmo Sud (2014-2018). Chimie des PM en Région PACA : études à Gardanne, Nice,
Marseille, et Port de Bouc.
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from app_main import app
layout = dbc.Container(
dbc.Row(
id="main",
className="h-100 acknowledgments-container",
children=[
dbc.Col(
[
dcc.Markdown(
open("./acknowledgments.md", "r").read(),
dangerously_allow_html=True
)
]
),
dbc.Col(
html.Div(
[
html.Img(width=250, className="img_logo", src="assets/img/logo_atmo_aura.png"),
html.Img(width=200, className="img_logo", src="assets/img/logo_atmo_sud.png"),
html.Img(width=200, className="img_logo", src="assets/img/logo_atmo_hdf.png"),
html.Img(width=100, className="img_logo", src="assets/img/logo_atmo_grandest.png"),
html.Img(width=220, className="img_logo", src="assets/img/logo_atmo_na.png"),
html.Img(width=200, className="img_logo", src="assets/img/logo_LCSQA.png"),
html.Img(width=150, className="img_logo", src="assets/img/logo_ADEME.png"),
html.Img(width=120, className="img_logo", src="assets/img/logo_IGE.png"),
html.Img(width=200, className="img_logo", src="assets/img/logo_IMTLD.png"),
html.Img(width=200, className="img_logo", src="assets/img/logo_INERIS.png")
],
className="d-flex flex-column align-items-center"
),
md=3,
className="ml-2"
)
],
),
)
......@@ -1227,3 +1227,124 @@ def add_season(df, month=True, month_to_season=None):
# and return the new dataframe
return dfnew
def get_OP_contribution(stations, species):
"""Get the OP contribution in nmol/min/m3 for the stations and OP in species
"""
DBPATH = "./data/deconvolOP.db"
queryOPobs = "SELECT * FROM OP;"
queryOPi = "SELECT * FROM OPi;"
querySRC = "SELECT * FROM SRC;"
conn = sqlite3.connect(DBPATH)
OP = pd.read_sql(queryOPobs, con=conn, parse_dates=["Date"]).set_index(["Station", "Date"])
df = pd.read_sql(queryOPi, con=conn).set_index(["Station", "Factor"]).T
SRC = pd.read_sql(querySRC, con=conn, parse_dates=["Date"]).set_index(["Station", "Date"])
SRC.index.names = ["Station", "Date"]
conn.close()
# # stations = settings.STATION_ORDER
# stations = ["GRE-cb", "PAS"]
contrib = pd.DataFrame()#index=SRC.index, columns=SRC.columns)
for station in stations:
contribtmp = SRC.loc[station] * df.loc[species[0], station]
contribtmp["Station"] = station
contrib = contrib.append(contribtmp)
colors = {}
factors = set(contrib.columns)-set(["Date"])
for factor in factors:
colors[factor] = get_sourceColor(source=factor)
contrib = contrib.dropna(how="all", axis=1)
contrib = add_season(contrib)
contrib = contrib.reset_index().melt(id_vars=["Station", "Date", "season", "month"],
var_name="Factor", value_name="OP")
return (contrib, factors, colors, OP)
def get_main_figure():
sources = [
"Biomass_burning",
"Dust",
"MSA_rich",
"Nitrate_rich",
"Primary_biogenic",
"Road traffic",
"Aged_salt",
"Sulfate_rich",
]
stations = [
"AIX",
"CHAM",
"GRE-cb",
"GRE-fr_2013",
"GRE-fr_2017",
"MNZ",
"MRS-5av",
"NGT",
"NIC",
"PAS",
"PdB",
"RBX",
"STG-cle",
"TAL",
"VIF",
]
OPtypes = ["DTTv", "AAv"]
contrib = pd.DataFrame()
for OPtype in OPtypes:
contribtmp, factors, colors, OP = get_OP_contribution(stations, [OPtype])
contribtmp["value"] = contribtmp["OP"]
contribtmp["var"] = OPtype
contrib = contrib.append(contribtmp)
contrib.drop("OP", axis=1, inplace=True)
DBPATH = "./data/deconvolOP.db"
querySRC = "SELECT * FROM SRC;"
conn = sqlite3.connect(DBPATH)
SRC = pd.read_sql(querySRC, con=conn, parse_dates=["Date"]).set_index(["Station", "Date"])
SRC.index.names = ["Station", "Date"]
conn.close()
contribtmp = SRC[sources]
contribtmp["var"] = "mass"
contribtmp = contribtmp.reset_index().melt(
id_vars=["Station", "var", "Date"], value_name="value", var_name="Factor"
)
contribtmp = add_season(contribtmp)
contrib = contrib.append(contribtmp)
contrib.replace({"AAv": "OP AA", "DTTv": "OP DTT"}, inplace=True)
contrib = contrib.loc[contrib["Factor"].isin(sources)]
monthly = contrib.groupby(["month", "Factor", "var"]).mean().reset_index()
fig = px.bar(
data_frame=monthly,
x="month",
y="value",
color="Factor",
facet_col="var",
category_orders={"var": ["mass", "OP DTT", "OP AA"]},
color_discrete_map=colors,
facet_col_spacing=0.07,
title="Monthly mean contribution of the 8 major sources to the mass, OP<sup>DTT</sup> and OP<sup>AA</sup> of PM10",
labels={"var": "Metric", "value": "Mean contribution", "Factor": "Source"}
)
fig.update_yaxes(
matches=None,
rangemode="nonnegative",
showticklabels=True,
)
fig.update_yaxes(col=1, title="µg.m⁻³")
fig.update_yaxes(col=2, title="nmol.min⁻¹.m⁻³")
fig.update_yaxes(col=3, title="nmol.min⁻¹.m⁻³")
fig.update_yaxes(title_standoff=0.2)
fig.update_xaxes(
title="",
tickvals=list(range(1, 13)),
ticktext=list("JFMAMJJASOND")
)
graph = dcc.Graph(figure=fig)
return graph
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import dash_html_components as html
import apps.app_components as ac
# from app_main import app
layout = dbc.Container(
dbc.Row([
dbc.Row(
id="main",
className="",
children=[
dbc.Col(
id="about-col",
children=[
html.H1("Source apportionement of the Oxidative Potential of aerosols"),
html.H2("About"),
html.H2("Source apportionement of the Oxidative Potential of aerosols"),
html.H3("About"),
dcc.Markdown("""
This application lets you browse the datasets and results obtained within the
framework of the *Get OP stand OP* ANR program. It gather the measurement of Oxidative Potential (OP) of
framework of the *Get OP stand OP* ANR program. It gathers the measurement of Oxidative Potential (OP) of
aerosols, sampled during several French research program, and attribute the
instrinsic OP of the different sources of aerosols, together with their relative
contribution to the observed OP.
This visualisation tool acts also as a supplementary information for a reasearch
manuscript submitted to *Atmospheric Chemistry and Physic*:
[Source apportionment of the oxidative potential of aerosols at 15 French sites
for yearly time series of observation](#ref), part of the PhD thesis of Samuël
manuscript submitted to *XXX*:
[Oxidative potential source apportionment at 15 French sites for yearly time series of observation](#ref), part of the PhD thesis of Samuël
Weber.
"""),
dcc.Link("Start exploring the results", href="/results",
className="btn btn-success mx-auto my-3 w-50 d-block"),
html.H2("Introduction"),
html.H3("Introduction"),
dcc.Markdown("""
Atmospheric ambient arerosols (or particulate matter, PM) has already been shown to
be linked to diverse health outcome such as asthma,
......@@ -63,7 +66,7 @@ in order to assess the geochemical stability of the intrinsic OP of the
factors at the regional level.
""", dangerously_allow_html=True),
html.H1("Main highlights"),
html.H3("Main highlights"),
dcc.Markdown("""
* Both OP present clear seasonal pattern, notably for alpine cities, with high
value during winter and relatively lower during summer.
......@@ -71,27 +74,15 @@ factors at the regional level.
observed.
* The 2 different OP assays do not present the same reactivity toward the
sampled aerosols.
* Comparing to the PM mass source apportionment, we clearly observe a
redistribution of the different sources contribution when
considering the OP instead of the mass.
* We also show that the primary road traffic source has an intrinsic OP (OP per μg of PM)
significantly higher than the other PM sources, notably
when considering the AA assay;
* Comparing to the PM mass source apportionment, we clearly observe a redistribution of
the different sources contribution when considering the OP instead of the mass.
* We also show that the primary road traffic source has an intrinsic OP (OP per μg of
PM) significantly higher than the other PM sources, notably when considering the AA
assay;
* As a consequence of the different intrinsic OP, the contribution of the
different sources highly depend on the metric we use (ie. mass or OP).
different sources highly depends on the metric we use (ie. mass or OP).
"""),
html.H2("Acknowledgments"),
dcc.Markdown("""
Obviously, this work and the >1700 samples collected at
15 different sampling sites was only possible thanks to
the dedicated effort of many people, notably in the
AASQA, for collecting the samples as well as the work of
many technicians and engineers in the IGE and INERIS
labs. The authors would like to kindly thank them for
all this work.
"""),
dcc.Link("Start exploring the results", href="/results",
className="btn btn-success mx-auto my-3 w-50 d-block")
ac.get_main_figure()
]
)
])
......
......@@ -1741,37 +1741,6 @@ def update_op_obs_vs_model_graph(stations, OPtypes):
return fig, fig_residual
def get_OP_contribution(stations, species):
"""Get the OP contribution in nmol/min/m3 for the stations and OP in species
"""
queryOPobs = "SELECT * FROM OP;"
queryOPi = "SELECT * FROM OPi;"
querySRC = "SELECT * FROM SRC;"
conn = sqlite3.connect(DBPATH)
OP = pd.read_sql(queryOPobs, con=conn, parse_dates=["Date"]).set_index(["Station", "Date"])
df = pd.read_sql(queryOPi, con=conn).set_index(["Station", "Factor"]).T
SRC = pd.read_sql(querySRC, con=conn, parse_dates=["Date"]).set_index(["Station", "Date"])
SRC.index.names = ["Station", "Date"]
conn.close()
# # stations = settings.STATION_ORDER
# stations = ["GRE-cb", "PAS"]
contrib = pd.DataFrame()#index=SRC.index, columns=SRC.columns)
for station in stations:
contribtmp = SRC.loc[station] * df.loc[species[0], station]
contribtmp["Station"] = station
contrib = contrib.append(contribtmp)
colors = {}
factors = set(contrib.columns)-set(["Date"])
for factor in factors:
colors[factor] = ac.get_sourceColor(source=factor)
contrib = contrib.dropna(how="all", axis=1)
contrib = ac.add_season(contrib)
contrib = contrib.reset_index().melt(id_vars=["Station", "Date", "season", "month"],
var_name="Factor", value_name="OP")
return (contrib, factors, colors, OP)
@app.callback(Output('op-contribution-graph', 'figure'),
[
Input('station-dropdown', 'value'),
......@@ -1805,7 +1774,7 @@ def update_op_contribution_graph(stations, species, plot_type):
to_return["layout"]["title"] = "Select at least one OP (DTTv or AAv), and only 1 at a time"
return to_return
contrib, factors, colors, OP = get_OP_contribution(stations, species)
contrib, factors, colors, OP = ac.get_OP_contribution(stations, species)
if plot_type == "daily":
id_vars = ["Factor"]
......@@ -1893,7 +1862,7 @@ def update_op_contribution_ts_graph(stations, species, plot_type, estimator):
to_return["layout"]["title"] = "Please, select at least 1 station."
return to_return
contrib, factors, colors, OP = get_OP_contribution(stations, species)
contrib, factors, colors, OP = ac.get_OP_contribution(stations, species)
if plot_type == "daily":
fig = px.bar(
......
......@@ -88,3 +88,14 @@ p {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.acknowledgments-container .img_logo {
display: block;
margin: 10px;
}
.acknowledgments-container ol li {
margin: 0.5rem 0;
}
......@@ -6,7 +6,7 @@ from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from app_main import app
from apps import app_contact, app_results, app_deconvolOP, app_estimateOP
from apps import app_contact, app_results, app_deconvolOP, app_estimateOP, app_acknowledgments
server = app.server
......@@ -27,7 +27,18 @@ app.layout = html.Div([
className="btn btn-primary align-items-center"),
dbc.NavLink("Estimate your OP", href="/estimate",
className="btn btn-link"),
dbc.NavLink("Question & contact", href="/contact", className="ml-auto"),
dbc.NavLink(
children=[
html.Span("Acknowledgments", className="d-none d-lg-inline mr-2"),
html.I(className="fa fa-users fa-2x align-middle"),
],
href="/acknowledgments", className="ml-auto"),
dbc.NavLink(
children=[
html.Span("Contact", className="d-none d-lg-inline mr-2"),
html.I(className="fa fa-at fa-2x align-middle"),
],
href="/contact", className=""),
],
navbar=True,
id='navbar-collapse'
......@@ -42,27 +53,27 @@ app.layout = html.Div([
),
href="http://www.ige-grenoble.fr"
),
html.A(
html.Img(
src="/assets/img/logo_INERIS.png",
className="logo", height=50
),
href="https://www.ineris.fr"
),
html.A(
html.Img(
src="/assets/img/logo_LCSQA.png",
className="logo", height=50
),
href="http://www.lcsqa.org"
),
html.A(
html.Img(
src="/assets/img/logo_ADEME.png",
className="logo", height=50
),
href="https://ademe.fr"
),
# html.A(
# html.Img(
# src="/assets/img/logo_INERIS.png",
# className="logo", height=50
# ),
# href="https://www.ineris.fr"
# ),
# html.A(
# html.Img(
# src="/assets/img/logo_LCSQA.png",
# className="logo", height=50
# ),
# href="http://www.lcsqa.org"
# ),
# html.A(
# html.Img(
# src="/assets/img/logo_ADEME.png",
# className="logo", height=50
# ),
# href="https://ademe.fr"
# ),
],
id="nav-logo",
className="ml-auto d-none d-md-block"
......@@ -98,6 +109,8 @@ def display_page(pathname):
return app_estimateOP.layout
elif pathname[:len('/contact')] == '/contact':
return app_contact.layout
elif pathname[:len('/acknowledgments')] == '/acknowledgments':
return app_acknowledgments.layout
else:
return app_deconvolOP.layout
# # return '404'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment