Compare commits

..

31 Commits

Author SHA1 Message Date
6a6de3bfcd Add Plot screenshot to Readme 2020-01-11 01:59:50 +01:00
b41b72b08a Update Black Box diagram 2020-01-10 23:54:43 +01:00
a00ed940d8 Update ER diagram 2020-01-10 23:29:36 +01:00
ee9558a1bb Add plotting functionality 2020-01-10 23:29:23 +01:00
6baa8779ca Convert Readme to Markdown 2020-01-10 14:11:21 +01:00
f599766609 Correct typo 2020-01-10 12:21:29 +01:00
d93bfa4428 Fit screenshot to page 2020-01-10 12:20:32 +01:00
2f0b39ef76 Add Table screenshot to Readme 2020-01-10 12:08:56 +01:00
aeb5c8b839 Update Readme and Citation 2020-01-10 11:59:54 +01:00
1e52bcae0d Show all matching annual data for glacier's name 2020-01-09 22:30:00 +01:00
65d3af6817 Move query logic to new module 2020-01-09 21:10:27 +01:00
c6e63cf087 Add Glacier name search for table querying 2020-01-09 20:56:17 +01:00
1aac68473d Add table querying via form 2020-01-09 19:13:24 +01:00
6caca22472 Prettify frontend with a CSS template 2020-01-09 08:06:18 +01:00
667bed6edf Add Bootstrap simple template and error handling 2020-01-09 05:20:50 +01:00
15ac576058 Add login functionality and basic HTML pages 2020-01-09 03:17:31 +01:00
a0a5384e5b Improve existing data check in 'export' 2020-01-08 22:40:17 +01:00
16a789cdf9 Only parse and export data if needed 2020-01-08 22:14:16 +01:00
f84d71616a Create DB when executing 'flask run' 2020-01-08 21:31:00 +01:00
712c84b358 Add Flask webapp draft 2020-01-08 05:51:10 +01:00
b7e7c3400d Remove 'annual_change' table 2020-01-08 03:21:11 +01:00
6a4af60d53 Change Integer values to Float 2020-01-08 02:22:41 +01:00
cbc298a629 Fix duplicate 'users' table insertion 2020-01-08 01:47:12 +01:00
7b945e2b98 Add SQL insertion functionality 2020-01-08 01:23:00 +01:00
dfd13f6037 Replace 'CHANGE' database with 'STATE' 2020-01-06 11:12:55 +01:00
6ef88e9b23 Create different DBs for each table 2020-01-06 08:31:21 +01:00
cbc1c08350 Add csv parser 2020-01-06 06:34:35 +01:00
8f31f6ff84 Create constants file and move tables to db_setup 2020-01-05 02:51:14 +01:00
c2d6c2363b Rename code folder and add filters to parser 2020-01-05 01:00:06 +01:00
7c4ba27dc5 Rename folders, add DB parser and improve DB setup 2020-01-04 17:44:51 +01:00
b7a6b4c9d6 Update WGMS version 2020-01-03 20:51:57 +01:00
69 changed files with 20670 additions and 91245 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
Assets/__titlepage.filled.tex
Design.org
Code/.env

View File

@@ -1,8 +0,0 @@
@misc{wgms-db,
author = {World Glacier Monitoring Service (WGMS)},
title = {{Fluctuations of Glaciers Database}},
publisher = {World Glacier Monitoring Service (WGMS)},
year = 2018,
month = nov # "~3,",
doi = {10.5904/wgms-fog-2018-11},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
(TeX-add-style-hook
"Citations"
(lambda ()
(LaTeX-add-bibitems
"wgms-db"))
:bibtex)

112
Code/Pipfile.lock generated
View File

@@ -1,112 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "77464f98cb5a9b5d12663323acdbbc44b448b5d9d2fee7c8e0e6302987ef8bfe"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"flask": {
"hashes": [
"sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
"sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
],
"index": "pypi",
"version": "==1.1.1"
},
"flask-sqlalchemy": {
"hashes": [
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",
"sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d"
],
"index": "pypi",
"version": "==2.4.1"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
],
"version": "==2.10.3"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
],
"version": "==1.1.1"
},
"pymysql": {
"hashes": [
"sha256:3943fbbbc1e902f41daf7f9165519f140c4451c179380677e6a848587042561a",
"sha256:d8c059dcd81dedb85a9f034d5e22dcb4442c0b201908bede99e306d65ea7c8e7"
],
"index": "pypi",
"version": "==0.9.3"
},
"sqlalchemy": {
"hashes": [
"sha256:bfb8f464a5000b567ac1d350b9090cf081180ec1ab4aa87e7bca12dab25320ec"
],
"version": "==1.3.12"
},
"werkzeug": {
"hashes": [
"sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7",
"sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"
],
"version": "==0.16.0"
}
},
"develop": {}
}

View File

@@ -1,25 +0,0 @@
from os import environ
from sqlalchemy import create_engine
def create_connection():
db_name = get_env_variable("DB_NAME")
db_user = get_env_variable("DB_USER")
db_password = get_env_variable("DB_PASSWORD")
db_url = get_env_variable("DB_URL")
db_connection_uri = "mysql+pymysql://{user}:{pw}@{url}/{db}".format(
user=db_user, pw=db_password, url=db_url, db=db_name
)
engine = create_engine(db_connection_uri, echo=True)
def create_tables():
def get_env_variable(name):
try:
return environ[name]
except KeyError:
message = "Expected environment variable '{}' not set.".format(name)
raise Exception(message)

View File

@@ -11,8 +11,6 @@
*** TODO Improve second handout [0/2] [0%]
- [ ] Correct diagrams
- [ ] Add conceptual diagram
*** INACTIVE Update date in YAML automatically [0/1] [0%]
- [ ] Add to Makefile
*** DONE Type requirements handout [4/4] [100%]
CLOSED: [2019-09-27 Fri 14:54] SCHEDULED: <2019-09-27 Fri 23:55>
- [X] Problem description
@@ -44,16 +42,38 @@ CLOSED: [2019-11-01 Fri 00:34]
- [X] Functional
- [X] Black box
- [X] Entity-Relationship
*** CANCELLED Update date in YAML automatically [0/1] [0%]
CLOSED: [2020-01-10 Fri 17:54]
- [ ] Add to Makefile
** Implementation
*** TODO Backend [0/2] [0%]
**** TODO Database [2/3] [66%]
*** DONE Backend [4/4] [100%]
CLOSED: [2020-01-10 Fri 23:15]
**** DONE Flask Application [3/3] [100%]
CLOSED: [2020-01-10 Fri 23:15]
- [X] Plots with pandas
- [X] Login for admin
- [X] Tables with pandas
**** DONE Database [3/3] [100%]
CLOSED: [2020-01-03 Fri 00:44]
- [X] Connection
- [X] Creation from script
- [ ] Creation of tables via class
**** NEXT Flask framework
-[[https://flask.palletsprojects.com/en/1.1.x/patterns/#patterns][ Patterns]]
*** NEXT Parsing script [0/2] [0%]
- [ ] Select useful fiels with awk
- [ ] Arithmetic operations for comparisons
*** INACTIVE Frontend [0/1] [0%]
- [ ] [[https://adminlte.io/][Adminlte]]
- [X] Creation of tables via class
**** DONE Parser [3/3] [100%]
CLOSED: [2020-01-08 Wed 03:18]
- [X] Select useful fiels
- [X] Convert PU to Country (ISO 3166)
- [X] Insert into database
**** DONE Possible additions [2/2] [100%]
CLOSED: [2020-01-09 Thu 20:57]
- [X] Text search for glaciers
- [X] Form seach for a year
*** TODO Documentation [1/2] [50%]
- [ ] Code
- [X] Readme
*** DONE Deployment [1/1] [100%]
CLOSED: [2020-01-10 Fri 12:14]
- [X] Installation instructions
*** DONE Frontend [2/2] [100%]
CLOSED: [2020-01-09 Thu 11:09]
- [X] Flask-Bootstrap
- [X] Find CSS

View File

@@ -1,162 +0,0 @@
* Diagramas UML
** Functional
#+begin_src plantuml :file ../Assets/Diagrams/FD.png
:Script|
split
:Alta de un glaciar;
split again
:Inclusión de datos anuales;
split again
:Cálculo de las variaciones anuales;
split again
:Actualización de la base de datos;
split again
:Alta del administrador;
:Administrador|
:Resolución de conflictos;
endsplit
:IGDB|
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/FD.png]]
** Data-flow
#+begin_src plantuml :file ../Assets/Diagrams/DF1.png
:Script|
:Alta del Glaciar;
-> Nuevo glaciar;
:Glaciar<
:IGDB|
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/DF1.png]]
#+begin_src plantuml :file ../Assets/Diagrams/DF2.png
:Script|
:Inclusión de datos anuales;
-> Añadir información;
:Glaciar<
:IGDB|
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/DF2.png]]
#+begin_src plantuml :file ../Assets/Diagrams/DF3.png
:Script|
:Cálculo de las variaciones anuales;
:Inclusión de datos anuales;
-> Añadir información;
:Glaciar<
:IGDB|
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/DF3.png]]
#+begin_src plantuml :file ../Assets/Diagrams/DF4.png
:Script|
:Alta del administrador;
-> Nuevo administrador;
:Administrador<
:IGDB|
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/DF4.png]]
#+begin_src plantuml :file ../Assets/Diagrams/DF5.png
:Script|
:Actualización de la base de datos;
:Inclusión de datos anuales;
-> Añadir información;
:Glaciar<
:IGDB|
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/DF5.png]]
#+begin_src plantuml :file ../Assets/Diagrams/DF6.png
:Script|
:Administrador<
:Resolución de conflictos;
-> Selecciona los datos correctos;
:Actualización de la base de datos;
:IGDB|
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/DF6.png]]
** Black box
#+begin_src plantuml :file ../Assets/Diagrams/BB.png
@startuml
start
:WGMS|
:**Sistema**;
:IGDB|
end
@enduml
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/BB.png]]
** Entity Relationship
#+begin_src plantuml :file ../Assets/Diagrams/ER.png
@startuml
hide circle
skinparam linetype ortho
entity "Glacier" as e01 {
,* **glacier_id** : number <<generated>>
--
,*glacier_name : text
--
,*glacier_country: text
}
entity "Glacier yearly data" as e02 {
,* **glacier_id** : number <<FK>>
--
,*glacier_volume: number
--
,*glacier_area: number
--
,*glacier_thickness: number
--
,*glacier_year: number
}
entity "Glacier yearly fluctuation" as e03 {
,* **glacier_id** : number <<FK>>
--
,*glacier_area_change: number
--
,*glacier_volume_change: number
--
,*glacier_thickness_change: number
--
,*glacier_year: number <<FK>>
}
entity "Admin" as e04 {
,*admin_id : number <<generated>>
--
,*admin_creation_date: date
}
e01 ||..|{ e02
e02 ||..|| e03
e01 ||..|{ e03
e04 ||..o| e01
@enduml
#+end_src
#+RESULTS:
[[file:../Assets/Diagrams/ER.png]]

View File

@@ -1,7 +0,0 @@
(TeX-add-style-hook
"Citations"
(lambda ()
(LaTeX-add-bibitems
"wgms-db"))
:bibtex)

View File

@@ -1,10 +1,10 @@
## Source files
## (Adjust to your needs. Order of markdown files in $(SRC) matters!)
PANDOC = pandoc
DOC = Docs
META = Assets/metadata.yaml
DOC = docs
META = assets/metadata.yaml
SRC = $(DOC)/Project.md
BIBFILE = Assets/Citations.bib
BIBFILE = assets/Citations.bib
TARGET = $(DOC)/Project.pdf
@@ -17,7 +17,7 @@ TARGET = $(DOC)/Project.pdf
## Auxiliary files
## (Do not change!)
TITLEPAGE = titlepage.tex
TMP = Assets/$(TITLEPAGE:%.tex=__%.filled.tex)
TMP = assets/$(TITLEPAGE:%.tex=__%.filled.tex)
## Pandoc options
@@ -30,13 +30,13 @@ OPTIONS += --metadata-file=$(META)
OPTIONS += -M bibliography=$(BIBFILE)
OPTIONS += --listings
OPTIONS += --include-in-header=$(TMP)
OPTIONS += --resource-path=./Assets/Diagrams
OPTIONS += --resource-path=./assets/Diagrams
## Template variables
TEMPLATE_DL_DIR = .tmp_template_dl
CLEANTHESIS_TEMPLATE = Assets/cleanthesis.sty
CLEANTHESIS_TEMPLATE = assets/cleanthesis.sty
TEMPLATE_FILES = $(CLEANTHESIS_TEMPLATE)
@@ -52,7 +52,7 @@ cleanthesis: TEMPLATE_FILE += $(CLEANTHESIS_TEMPLATE)
cleanthesis: TEMPLATE_REPO += $(CLEANTHESIS_REPO)
cleanthesis: TEMPLATE_VERSION += $(CLEANTHESIS_VERSION)
cleanthesis: AUX_OPTS += -M cleanthesis=true -M cleanthesisbibfile=$(BIBFILE:%.bib=%)
cleanthesis: OPTIONS += --include-in-header=Assets/include-header.tex $(AUX_OPTS)
cleanthesis: OPTIONS += --include-in-header=assets/include-header.tex $(AUX_OPTS)
cleanthesis: $(CLEANTHESIS_TEMPLATE) $(TARGET)
@@ -80,7 +80,7 @@ ${TARGET}: $(SRC) $(META) $(BIBFILE) $(TMP)
## Build auxiliary files (title page, references)
$(TMP): Assets/__%.filled.tex: Assets/%.tex $(META)
$(TMP): assets/__%.filled.tex: assets/%.tex $(META)
$(PANDOC) $(AUX_OPTS) --template=$< --metadata-file=$(META) -o $@ $<

View File

@@ -6,9 +6,6 @@ verify_ssl = true
[dev-packages]
[packages]
flask = "*"
pymysql = "*"
flask-sqlalchemy = "*"
[requires]
python_version = "3.8"

75
README.md Normal file
View File

@@ -0,0 +1,75 @@
IGDB: Internation Glacier Database
==================================
The IGDB is a database, that uses data from the
[WGMS](https://dx.doi.org/10.5904/wgms-fog-2019-12) to illustrate the
consequences of climate change.
Our system allows you to visualize data with tables and plots, via our
intuitive Web UI.
![Table](./assets/screenshots/Table.png)
![Plot](./assets/screenshots/Plot.png)
Technologies used
-----------------
- Flask
- SQLAlchemy
- Pandas
Requirements
------------
- Python3
- POSIX shell
- Pip
- Pipenv
- MySQL/MariaDB
Installation
------------
1. Clone the repository:
``` bash
git clone https://coolneng.duckdns.org/gitea/coolneng/igdb
```
2. Change the working directory to the project:
``` bash
cd igdb/code
```
3. Install the dependencies:
``` bash
pipenv install
```
All the dependencies will be installed inside a virtualenv.
Usage
-----
1. Start a shell inside the virtualenv:
``` bash
pipenv shell
```
2. Start the Flask server:
``` bash
flask run
```
3. When prompted for a password, insert your MySQL/MariaDB root
password
The database will be created and populated automatically, if needed,
each time the server is executed
4. The website can be accessed via **localhost:5000**

View File

@@ -1,3 +0,0 @@
* IGDB: Internation Glacier Database
The IGDB is a database, that uses data from the [[http://dx.doi.org/10.5904/wgms-fog-2018-11][WGMS]] to illustrate the consequences of climate change.

8
assets/Citations.bib Normal file
View File

@@ -0,0 +1,8 @@
@misc{wgms-db,
author = {World Glacier Monitoring Service (WGMS)},
title = {{Fluctuations of Glaciers Database}},
publisher = {World Glacier Monitoring Service (WGMS)},
year = 2019,
month = dec # "~2,",
doi = {10.5904/wgms-fog-2019-12}
}

BIN
assets/Diagrams/BB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/Diagrams/ER.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,54 @@
%------------------------------------- Custom Title Page ---------------------------
\renewcommand{\maketitle}{
\thispagestyle{empty}
\parindent=0pt
\vspace{20mm}
\hrule
\vspace{5mm}
\begin{center}
\LARGE
\textbf{\textsc{ IGDB: Base de datos internacional de glaciares }}
\end{center}
\vspace{5mm}
\hrule
\vspace{10mm}
\begin{center}
\Large
\textsc{ Diseño y Desarrollo de Sistemas de Información }
\end{center}
\vfill
\noindent
\includegraphics[width=120pt, center]{/home/coolneng/Photos/Logos/UGR.png}
\vspace{\baselineskip}\noindent
\large
\begin{tabular}{lp{\textwidth}}
Autor: & \texttt{Amin Kasrou Aouam}\\
Fecha: & \texttt{10/01/2020}\\
&\\[24pt]
\end{tabular}
}
%------------------------------------- Custom Title Page ---------------------------
\usepackage[export]{adjustbox}
\usepackage{graphicx}
%------------------------------------- Workaround for CleanStyle -------------------
%% cleanthesis.sty *will* check the bibfile, even if `configurebiblatex=false` ...
%% So we need to set it appropriately using our metadata variable "cleanthesisbibfile"
\PassOptionsToPackage{
figuresep=colon,
configurelistings=true,
configurebiblatex=false,
bibfile=assets/Citations
}{cleanthesis}
%------------------------------------- Workaround for CleanStyle -------------------

View File

@@ -2,8 +2,8 @@
title: "IGDB: Base de datos internacional de glaciares"
subtitle: "Diseño y Desarrollo de Sistemas de Información"
author: [Amin Kasrou Aouam]
date: 18/10/2019
logo: /home/coolneng/Pictures/Logos/UGR.png
date: 10/01/2020
logo: /home/coolneng/Photos/Logos/UGR.png
lang: es-ES
toc: true
toc-own-page: true

BIN
assets/screenshots/Plot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

1
code/.env Normal file
View File

@@ -0,0 +1 @@
FLASK_APP=igdb.py

20
code/Pipfile Normal file
View File

@@ -0,0 +1,20 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
flask = "*"
pymysql = "*"
flask-sqlalchemy = "*"
pandas = "*"
iso3166 = "*"
flask-wtf = "*"
flask-login = "*"
flask-bootstrap = "*"
matplotlib = "*"
[requires]
python_version = "3.8"

309
code/Pipfile.lock generated Normal file
View File

@@ -0,0 +1,309 @@
{
"_meta": {
"hash": {
"sha256": "80f6c409aa1104974ff405c48d5527140030fb7e8d6bbf8aa21e884b3d151b5a"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"cycler": {
"hashes": [
"sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d",
"sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"
],
"version": "==0.10.0"
},
"dominate": {
"hashes": [
"sha256:6e833aea505f0236a9fc692326bac575f8bd38ae0f3a1bdc73d20ca606ac75d5",
"sha256:a92474b4312bd8b4c1789792f3ec8c571cd8afa8e7502a2b1c64dd48cd67e59c"
],
"version": "==2.4.0"
},
"flask": {
"hashes": [
"sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
"sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
],
"index": "pypi",
"version": "==1.1.1"
},
"flask-bootstrap": {
"hashes": [
"sha256:cb08ed940183f6343a64e465e83b3a3f13c53e1baabb8d72b5da4545ef123ac8"
],
"index": "pypi",
"version": "==3.3.7.1"
},
"flask-login": {
"hashes": [
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
],
"index": "pypi",
"version": "==0.4.1"
},
"flask-sqlalchemy": {
"hashes": [
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",
"sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d"
],
"index": "pypi",
"version": "==2.4.1"
},
"flask-wtf": {
"hashes": [
"sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36",
"sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac"
],
"index": "pypi",
"version": "==0.14.2"
},
"iso3166": {
"hashes": [
"sha256:b07208703bd881a4f974e39fa013c4498dddd64913ada15f24be75d02ae68a44",
"sha256:b1e58dbcf50fbb2c9c418ec7a6057f0cdb30b8f822ac852f72e71ba769dae8c5"
],
"index": "pypi",
"version": "==1.0.1"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
],
"version": "==2.10.3"
},
"kiwisolver": {
"hashes": [
"sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f",
"sha256:210d8c39d01758d76c2b9a693567e1657ec661229bc32eac30761fa79b2474b0",
"sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7",
"sha256:3b15d56a9cd40c52d7ab763ff0bc700edbb4e1a298dc43715ecccd605002cf11",
"sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe",
"sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c",
"sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5",
"sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75",
"sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187",
"sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641",
"sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883",
"sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5",
"sha256:76275ee077772c8dde04fb6c5bc24b91af1bb3e7f4816fd1852f1495a64dad93",
"sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2",
"sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3",
"sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389",
"sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897",
"sha256:9105ce82dcc32c73eb53a04c869b6a4bc756b43e4385f76ea7943e827f529e4d",
"sha256:933df612c453928f1c6faa9236161a1d999a26cd40abf1dc5d7ebbc6dbfb8fca",
"sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a",
"sha256:9491578147849b93e70d7c1d23cb1229458f71fc79c51d52dce0809b2ca44eea",
"sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c",
"sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326",
"sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0",
"sha256:aa716b9122307c50686356cfb47bfbc66541868078d0c801341df31dca1232a9",
"sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e",
"sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544",
"sha256:d22702cadb86b6fcba0e6b907d9f84a312db9cd6934ee728144ce3018e715ee1",
"sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995",
"sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f",
"sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee",
"sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004",
"sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2",
"sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9",
"sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a",
"sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f",
"sha256:fe51b79da0062f8e9d49ed0182a626a7dc7a0cbca0328f612c6ee5e4711c81e4"
],
"version": "==1.1.0"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
],
"version": "==1.1.1"
},
"matplotlib": {
"hashes": [
"sha256:08ccc8922eb4792b91c652d3e6d46b1c99073f1284d1b6705155643e8046463a",
"sha256:161dcd807c0c3232f4dcd4a12a382d52004a498174cbfafd40646106c5bcdcc8",
"sha256:1f9e885bfa1b148d16f82a6672d043ecf11197f6c71ae222d0546db706e52eb2",
"sha256:2d6ab54015a7c0d727c33e36f85f5c5e4172059efdd067f7527f6e5d16ad01aa",
"sha256:5d2e408a2813abf664bd79431107543ecb449136912eb55bb312317edecf597e",
"sha256:61c8b740a008218eb604de518eb411c4953db0cb725dd0b32adf8a81771cab9e",
"sha256:80f10af8378fccc136da40ea6aa4a920767476cdfb3241acb93ef4f0465dbf57",
"sha256:819d4860315468b482f38f1afe45a5437f60f03eaede495d5ff89f2eeac89500",
"sha256:8cc0e44905c2c8fda5637cad6f311eb9517017515a034247ab93d0cf99f8bb7a",
"sha256:8e8e2c2fe3d873108735c6ee9884e6f36f467df4a143136209cff303b183bada",
"sha256:98c2ffeab8b79a4e3a0af5dd9939f92980eb6e3fec10f7f313df5f35a84dacab",
"sha256:d59bb0e82002ac49f4152963f8a1079e66794a4f454457fd2f0dcc7bf0797d30",
"sha256:ee59b7bb9eb75932fe3787e54e61c99b628155b0cedc907864f24723ba55b309"
],
"index": "pypi",
"version": "==3.1.2"
},
"numpy": {
"hashes": [
"sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6",
"sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e",
"sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc",
"sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc",
"sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a",
"sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa",
"sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3",
"sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121",
"sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971",
"sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26",
"sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd",
"sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480",
"sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec",
"sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77",
"sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57",
"sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07",
"sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572",
"sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73",
"sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca",
"sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474",
"sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"
],
"version": "==1.18.1"
},
"pandas": {
"hashes": [
"sha256:00dff3a8e337f5ed7ad295d98a31821d3d0fe7792da82d78d7fd79b89c03ea9d",
"sha256:22361b1597c8c2ffd697aa9bf85423afa9e1fcfa6b1ea821054a244d5f24d75e",
"sha256:255920e63850dc512ce356233081098554d641ba99c3767dde9e9f35630f994b",
"sha256:26382aab9c119735908d94d2c5c08020a4a0a82969b7e5eefb92f902b3b30ad7",
"sha256:33970f4cacdd9a0ddb8f21e151bfb9f178afb7c36eb7c25b9094c02876f385c2",
"sha256:4545467a637e0e1393f7d05d61dace89689ad6d6f66f267f86fff737b702cce9",
"sha256:52da74df8a9c9a103af0a72c9d5fdc8e0183a90884278db7f386b5692a2220a4",
"sha256:61741f5aeb252f39c3031d11405305b6d10ce663c53bc3112705d7ad66c013d0",
"sha256:6a3ac2c87e4e32a969921d1428525f09462770c349147aa8e9ab95f88c71ec71",
"sha256:7458c48e3d15b8aaa7d575be60e1e4dd70348efcd9376656b72fecd55c59a4c3",
"sha256:78bf638993219311377ce9836b3dc05f627a666d0dbc8cec37c0ff3c9ada673b",
"sha256:8153705d6545fd9eb6dd2bc79301bff08825d2e2f716d5dced48daafc2d0b81f",
"sha256:975c461accd14e89d71772e89108a050fa824c0b87a67d34cedf245f6681fc17",
"sha256:9962957a27bfb70ab64103d0a7b42fa59c642fb4ed4cb75d0227b7bb9228535d",
"sha256:adc3d3a3f9e59a38d923e90e20c4922fc62d1e5a03d083440468c6d8f3f1ae0a",
"sha256:bbe3eb765a0b1e578833d243e2814b60c825b7fdbf4cdfe8e8aae8a08ed56ecf",
"sha256:df8864824b1fe488cf778c3650ee59c3a0d8f42e53707de167ba6b4f7d35f133",
"sha256:e45055c30a608076e31a9fcd780a956ed3b1fa20db61561b8d88b79259f526f7",
"sha256:ee50c2142cdcf41995655d499a157d0a812fce55c97d9aad13bc1eef837ed36c"
],
"index": "pypi",
"version": "==0.25.3"
},
"pymysql": {
"hashes": [
"sha256:3943fbbbc1e902f41daf7f9165519f140c4451c179380677e6a848587042561a",
"sha256:d8c059dcd81dedb85a9f034d5e22dcb4442c0b201908bede99e306d65ea7c8e7"
],
"index": "pypi",
"version": "==0.9.3"
},
"pyparsing": {
"hashes": [
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
],
"version": "==2.4.6"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"version": "==2.8.1"
},
"pytz": {
"hashes": [
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
],
"version": "==2019.3"
},
"six": {
"hashes": [
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
],
"version": "==1.13.0"
},
"sqlalchemy": {
"hashes": [
"sha256:bfb8f464a5000b567ac1d350b9090cf081180ec1ab4aa87e7bca12dab25320ec"
],
"version": "==1.3.12"
},
"visitor": {
"hashes": [
"sha256:2c737903b2b6864ebc6167eef7cf3b997126f1aa94bdf590f90f1436d23e480a"
],
"version": "==0.1.3"
},
"werkzeug": {
"hashes": [
"sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7",
"sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"
],
"version": "==0.16.0"
},
"wtforms": {
"hashes": [
"sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61",
"sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1"
],
"version": "==2.2.1"
}
},
"develop": {}
}

14
code/app/__init__.py Normal file
View File

@@ -0,0 +1,14 @@
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_bootstrap import Bootstrap
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
login = LoginManager(app)
login.login_view = "login"
bootstrap = Bootstrap(app)
from app import routes, models, errors

13
code/app/errors.py Normal file
View File

@@ -0,0 +1,13 @@
from flask import render_template
from app import app, db
@app.errorhandler(404)
def not_found_error(error):
return render_template("404.html"), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template("500.html"), 500

31
code/app/forms.py Normal file
View File

@@ -0,0 +1,31 @@
from flask_wtf import FlaskForm
from wtforms import BooleanField, PasswordField, SelectField, StringField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()])
password = PasswordField("Password", validators=[DataRequired()])
remember_me = BooleanField("Remember Me")
submit = SubmitField("Sign In")
class AnnualForm(FlaskForm):
year_list = [
("2011", 2011),
("2012", 2012),
("2013", 2013),
("2014", 2014),
("2015", 2015),
("2016", 2016),
("2017", 2017),
("2018", 2018),
]
name = StringField("Search by Glacier Name")
year = SelectField("Search by Year", validators=[DataRequired()], choices=year_list)
submit = SubmitField("Search")
class PlotForm(FlaskForm):
name = StringField("Glacier Name", validators=[DataRequired()])
submit = SubmitField("Search")

51
code/app/models.py Normal file
View File

@@ -0,0 +1,51 @@
from app import db, login
from flask_login import UserMixin
from werkzeug.security import check_password_hash
class Glacier(db.Model):
id = db.Column(db.String(20), primary_key=True)
country = db.Column(db.String(60))
name = db.Column(db.String(60))
annual_data = db.relationship("Annual_Data")
def __init__(self, id, country, name):
self.id = id
self.country = country
self.name = name
class Annual_Data(db.Model):
__tablename__ = "annual_data"
year = db.Column(db.Integer, primary_key=True)
id = db.Column(db.ForeignKey("glacier.id"), primary_key=True)
surface = db.Column(db.Float)
length = db.Column(db.Float)
elevation = db.Column(db.Float)
def __init__(self, year, surface, length, elevation):
self.year = year
self.surface = surface
self.length = length
self.elevation = elevation
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
registration_date = db.Column(
db.DateTime, nullable=False, server_default=db.func.now()
)
username = db.Column(db.String(20), nullable=False, unique=True)
password_hash = db.Column(db.String(128), unique=True)
def __init__(self, id, username, password_hash):
self.id = id
self.username = username
self.password_hash = password_hash
def check_password(self, password):
return check_password_hash(self.password_hash, password)
@login.user_loader
def load_user(id):
return User.query.get(int(id))

90
code/app/routes.py Normal file
View File

@@ -0,0 +1,90 @@
from app import app
from app.forms import AnnualForm, LoginForm, PlotForm
from database.queries import query_annual_data, query_plot_data, query_user
from flask import flash, redirect, render_template, request, url_for, send_file
from flask_login import current_user, login_required, login_user, logout_user
from processing.dataframe import create_table, create_plot
from werkzeug.urls import url_parse
@app.route("/")
@app.route("/index")
def index():
return render_template("index.html", title="Home Page")
@app.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
return redirect(url_for("admin"))
form = LoginForm()
if form.validate_on_submit():
user = query_user(form)
if user is None or not user.check_password(form.password.data):
flash("Invalid username or password")
return redirect(url_for("login"))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get("next")
if not next_page or url_parse(next_page).netloc != "":
next_page = url_for("admin")
return redirect(next_page)
return render_template("login.html", title="Sign In", form=form)
@app.route("/logout")
def logout():
logout_user()
return redirect(url_for("index"))
@app.route("/admin")
@login_required
def admin():
return render_template("admin.html", title="Admin Page")
@app.route("/nuke")
@login_required
def nuke():
return render_template("nuked.html", title="NUKE THE SYSTEM!")
@app.route("/data")
def data():
return render_template("data.html", title="Data")
@app.route("/table_selection", methods=["GET", "POST"])
def table_selection():
form = AnnualForm()
if form.validate_on_submit():
query = query_annual_data(form)
table = create_table(query.statement)
return render_template("table.html", table=table, title="Table")
return render_template("table_selection.html", title="Data", form=form)
@app.route("/table")
def table():
return render_template("table.html", table=table, title="Table")
@app.route("/plot_selection", methods=["GET", "POST"])
def plot_selection():
form = PlotForm()
if form.validate_on_submit():
query = query_plot_data(form)
plot = create_plot(query.statement)
return render_template("plot.html", title="Plot", plot=plot)
return render_template("plot_selection.html", title="Data", form=form)
@app.route("/plot")
def plot():
return render_template("plot.html", title="Plot", plot=plot)
@app.route("/figure")
def figure(query):
fig = create_plot(query)
return send_file(fig, mimetype="image/png")

12
code/app/static/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
code/app/static/plot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block app_content %}
<h1>Sorry, we couldn't find that</h1>
<p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}

View File

@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block app_content %}
<h1>An unexpected error has occurred</h1>
<p>The administrator has been notified!</p>
<p>If he gets too many notifications, we might replace him with an AI</p>
<p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<h1 class="text-center">Hey, {{ current_user.username }}!</h1>
<p class="text-center">Do you want to nuke the database?</p>
<li>
<p class="text-center">
<a href="{{ url_for('nuke') }}"><b>Yes</b>, I want to burn down the world</a>
</p>
</li>
<li>
<p class="text-center">
Nah, show me some cool <a href="{{ url_for('data') }}">data</a>
</p>
</li>
{% endblock %}

View File

@@ -0,0 +1,55 @@
{% extends 'bootstrap/base.html' %}
{% block title %}
{% if title %}{{ title }} - IGDB{% else %}IGDB{% endif %}
{% endblock %}
{% block styles %}
<link rel="stylesheet"
href="{{url_for('.static', filename='bootstrap.min.css')}}">
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="{{ url_for('index') }}">IGDB</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor01">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
</li>
{% if current_user.is_anonymous %}
<li class="nav-link"><a href="{{ url_for('data') }}">Data</a></li>
{% else %}
<li class="nav-link"><a href="{{ url_for('data') }}">Data</a></li>
<li class="nav-link"><a href="{{ url_for('admin') }}">Administration</a></li>
{% endif %}
</ul>
<ul class="navbar-nav navbar-right">
{% if current_user.is_anonymous %}
<li class="nav-link"><a href="{{ url_for('login') }}">Login</a></li>
{% else %}
<li class="nav-item"><a href="{{ url_for('logout') }}">Logout</a></li>
{% endif %}
</ul>
</div>
</nav>
{% endblock %}
{% block content %}
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info" role="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{# application content needs to be provided in the app_content block #}
{% block app_content %}{% endblock %}
</div>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block content %}
<h1 class="text-center">What do you want to check out?</h1>
<li>
<p class="text-center">
<a href="{{ url_for('table_selection') }}" class="btn btn-success" role="button" aria-pressed="true">Tables</a>
</p>
</li>
<li>
<p class="text-center">
<a href="{{ url_for('plot_selection') }}" class="btn btn-success" role="button" aria-pressed="true">Plots</a>
</p>
</li>
{% endblock %}

View File

@@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="jumbotron">
<h1 id="igdb-internation-glacier-database">IGDB: Internation Glacier Database</h1>
<p>The IGDB is a database, that uses data from the <a href="http://dx.doi.org/10.5904/wgms-fog-2018-11">WGMS</a> to illustrate the consequences of climate change.</p>
<p>Our system allows you to visualize data with tables and plots, via our intuitive Web UI.</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Sign In</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
<br>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block content %}
<h1 id="youre-the-boss">You're the boss!</h1>
<ol type="1">
<li>Stop the web server, and then execute:</li>
<pre class="shell"><code>mysql -u root -p</code></pre>
<li><p>Introduce MySQL's root password</p></li>
<li><p>Execute these statements:</p></li>
<div class="sourceCode" id="cb2"><pre class="sourceCode sql"><code class="sourceCode sql"><span id="cb2-1"><a href="#cb2-1"></a><span class="kw">DROP</span> <span class="kw">DATABASE</span> IGDB;</span>
<span id="cb2-2"><a href="#cb2-2"></a><span class="kw">DROP</span> <span class="fu">USER</span> IGDB@LOCALHOST;</span></code></pre></div>
{% endblock %}
</ol>

View File

@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Plot</h1>
<img src="data:image/png;base64,{{ plot }}" alt="Image Placeholder">
<p><a href="{{ url_for('plot_selection') }}">Back</a></p>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Plot selection</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
<br>
{% endblock %}

View File

@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Table</h1>
{{ table|safe }}
<p><a href="{{ url_for('table_selection') }}">Back</a></p>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Table selection</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
<br>
{% endblock %}

7
code/config.py Normal file
View File

@@ -0,0 +1,7 @@
from database.constants import CONNECTION_URI
class Config(object):
SQLALCHEMY_DATABASE_URI = CONNECTION_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "trolaso"

View File

View File

@@ -0,0 +1,8 @@
DB_NAME = "igdb"
DB_USER = "igdb"
DB_PW = "agentorange"
ADMIN_PW = "fuckmonsanto"
HOST = "localhost:3306"
CONNECTION_URI = "mysql+pymysql://{user}:{pw}@{url}/{db}".format(
user=DB_USER, pw=DB_PW, url=HOST, db=DB_NAME
)

24
code/database/db_setup.py Normal file
View File

@@ -0,0 +1,24 @@
from app import db
from database.constants import DB_NAME, DB_USER, DB_PW
from subprocess import run
def create_database():
script = "database/mariadb_setup.sh"
output = run([script, DB_NAME, DB_USER, DB_PW])
if output.returncode != 0:
print("Error: couldn't create database")
exit()
def create_tables():
db.create_all()
def main():
create_database()
create_tables()
if __name__ == "__main__":
main()

33
code/database/export.py Normal file
View File

@@ -0,0 +1,33 @@
from app import db
from app.models import Annual_Data, Glacier, User
from pandas import DataFrame, read_csv
def create_dataframes() -> {DataFrame}:
files = {
"glacier": "../data/glacier.csv",
"annual_data": "../data/annual_data.csv",
"user": "../data/user.csv",
}
df_list = {}
for csv in files.keys():
df_list[csv] = read_csv(files[csv])
return df_list
def insert_data(df_list):
models = [Glacier, Annual_Data, User]
for model in models:
if model.query.first() is not None:
return
for key, value in df_list.items():
value.to_sql(key, con=db.engine, index=False, if_exists="append")
def main():
df_list = create_dataframes()
insert_data(df_list)
if __name__ == "__main__":
main()

View File

@@ -2,7 +2,7 @@
EXPECTED_ARGS=3
ERROR=1
MYSQL=$(command -v which)
MYSQL=$(command -v mysql)
if [ $# -ne $EXPECTED_ARGS ]
then

84
code/database/parser.py Normal file
View File

@@ -0,0 +1,84 @@
from iso3166 import countries as co
from pandas import DataFrame, concat, read_csv
from csv import QUOTE_NONNUMERIC
from database.constants import ADMIN_PW
from os import path
from werkzeug.security import generate_password_hash
def country_conversion(political_unit) -> str:
if political_unit == "99":
return "99"
codes = co.get(political_unit)
return codes.name
def select_columns() -> DataFrame:
min_year = 2010
fields = [
"POLITICAL_UNIT",
"NAME",
"WGMS_ID",
"YEAR",
"MEDIAN_ELEVATION",
"AREA",
"LENGTH",
]
iter_csv = read_csv(
"../data/WGMS-FoG-2019-12-B-STATE.csv",
skipinitialspace=True,
usecols=fields,
iterator=True,
chunksize=100,
converters={"POLITICAL_UNIT": country_conversion},
)
data = concat([chunk[chunk["YEAR"] > min_year] for chunk in iter_csv])
return data
def rename_fields(df_list):
new_df_list = {}
new_fields = {
"POLITICAL_UNIT": "country",
"NAME": "name",
"WGMS_ID": "id",
"YEAR": "year",
"MEDIAN_ELEVATION": "elevation",
"AREA": "surface",
"LENGTH": "length",
}
for key, value in df_list.items():
new_df_list[key] = value.rename(columns=new_fields)
return new_df_list
def create_databases(df):
files = {
"glacier": "../data/glacier.csv",
"annual_data": "../data/annual_data.csv",
"user": "../data/user.csv",
}
user = {
"id": [7843],
"username": ["admin"],
"password_hash": [generate_password_hash(ADMIN_PW)],
}
dataframes = {
"glacier": df[["POLITICAL_UNIT", "NAME", "WGMS_ID"]].drop_duplicates(),
"annual_data": df[["WGMS_ID", "YEAR", "AREA", "MEDIAN_ELEVATION", "LENGTH"]],
"user": DataFrame(user),
}
renamed_dfs = rename_fields(dataframes)
for key, val in renamed_dfs.items():
if path.isfile(files[key]):
continue
val.to_csv(files[key], index=False, quoting=QUOTE_NONNUMERIC)
def main():
df = select_columns()
create_databases(df)
if __name__ == "__main__":
main()

38
code/database/queries.py Normal file
View File

@@ -0,0 +1,38 @@
from app import db
from app.models import Annual_Data, Glacier, User
from flask_sqlalchemy import BaseQuery
from flask import flash, redirect, url_for
def query_annual_data(form) -> BaseQuery:
annual_data = db.session.query(Annual_Data).filter_by(year=form.year.data)
if form.name.data:
glacier_name = (
db.session.query(Annual_Data)
.join(Glacier, Glacier.id == Annual_Data.id)
.filter_by(name=form.name.data)
.group_by(Annual_Data.year)
)
if glacier_name.first() is None:
flash("Sorry, no results found")
return redirect(url_for("table_selection"))
return glacier_name
return annual_data
def query_user(form) -> BaseQuery:
user = User.query.filter_by(username=form.username.data).first()
return user
def query_plot_data(form) -> BaseQuery:
query = (
db.session.query(Annual_Data)
.join(Glacier, Glacier.id == Annual_Data.id)
.filter_by(name=form.name.data)
.group_by(Annual_Data.year)
)
if query.first() is None:
flash("Sorry, no results found")
return redirect(url_for("plot_selection"))
return query

7
code/igdb.py Normal file
View File

@@ -0,0 +1,7 @@
from app import app
from database import db_setup, export, parser
from processing import dataframe
db_setup.main()
parser.main()
export.main()

View File

View File

@@ -0,0 +1,45 @@
from app import db
from io import BytesIO
from pandas import DataFrame, read_sql
from base64 import b64encode
def create_dataframe(query) -> DataFrame:
df = read_sql(sql=query, con=db.engine)
return df
def render_table(df) -> str:
df.fillna(value=0, inplace=True)
table = df.to_html(classes=["table-striped", "table-hover"])
return table
def render_plot(df):
df.fillna(value=0, inplace=True)
plot = df.plot("year", ["surface", "length", "elevation"], kind="bar")
plot_figure = plot.get_figure()
figure = BytesIO()
plot_figure.savefig(figure)
figure.seek(0)
return figure
def encode_plot(plot):
buffer = b"".join(plot)
buf = b64encode(buffer)
encoded_plot = buf.decode("utf-8")
return encoded_plot
def create_table(query) -> str:
df = create_dataframe(query)
html_table = render_table(df)
return html_table
def create_plot(query):
df = create_dataframe(query)
plot = render_plot(df)
encoded_plot = encode_plot(plot)
return encoded_plot

File diff suppressed because it is too large Load Diff

View File

@@ -32,24 +32,23 @@ Requisitos
### Datos
1. **RD1**: Datos del glaciar
- País - *Cadena de 30 caracteres máximo*
- Nombre del glaciar - *Cadena de 30 caracteres máximo*
- ID del glaciar (Compatible con la WGMS) - *Entero de 5 dígitos*
- País - *Cadena de 60 caracteres máximo*
- Nombre del glaciar - *Cadena de 60 caracteres máximo*
- ID del glaciar (Compatible con la WGMS) - *Cadena de 20
caracteres*
2. **RD2**: Datos anuales de un glaciar
- ID del glaciar (Compatible con la WGMS) - *Entero de 5 dígitos*
- Área - *Entero de 10 dígitos*
- Volumen - *Entero de 10 dígitos*
- Grosor - *Entero de 10 dígitos*
- Año - *Entero de 10 dígitos*
3. **RD3**: Datos de cambio de un glaciar
- ID del glaciar (Compatible con la WGMS) - *Entero de 5 dígitos*
- Variación de área - *Entero de 10 dígitos*
- Variación de volumen - *Entero de 10 dígitos*
- Variación de grosor - *Entero de 10 dígitos*
- Año - *Entero de 10 dígitos*
4. **RD4**: Datos del administrador
- ID - *Entero de 4 dígitos*
- Fecha de alta - *Fecha en formato dd-mm-yyyy*
- ID del glaciar (Compatible con la WGMS) - *Cadena de 20
caracteres*
- Área - *Decimal*
- Volumen - *Decimal*
- Altura - *Decimal*
- Año - *Entero de 11 dígitos*
3. **RD3**: Datos del administrador
- ID - *Entero de 11 dígitos*
- Fecha y hora de alta - *Fecha y hora en formato yyyy-mm-dd
hh:mm*
- Nombre de usuario - *Cadena de 20 caracteres máximo*
- Hash de la contraseña - *Cadena de 128 caracteres máximo*
### Funcionales
@@ -67,7 +66,7 @@ Requisitos
3. **RF3**: Cálculo de las variaciones anuales
Calcula las variaciones anuales de grosor, área y volumen para un
Calcula las variaciones anuales de altura, área y longitud para un
glaciar
- Entrada: **RD2**
@@ -161,9 +160,9 @@ Ingeniería del Software.
![IGDB](./BB.png)
### Diagrama Entidad-Relación
\clearpage
\newpage
### Diagrama Entidad-Relación
![](./ER.png)

View File

@@ -18,27 +18,22 @@ datos relevantes para estudios acerca del cambio climático, y acotando éstos a
*** Datos
1. *RD1*: Datos del glaciar
- País - /Cadena de 30 caracteres máximo/
- Nombre del glaciar - /Cadena de 30 caracteres máximo/
- ID del glaciar (Compatible con la WGMS) - /Entero de 5 dígitos/
- País - /Cadena de 60 caracteres máximo/
- Nombre del glaciar - /Cadena de 60 caracteres máximo/
- ID del glaciar (Compatible con la WGMS) - /Cadena de 20 caracteres/
2. *RD2*: Datos anuales de un glaciar
- ID del glaciar (Compatible con la WGMS) - /Entero de 5 dígitos/
- Área - /Entero de 10 dígitos/
- Volumen - /Entero de 10 dígitos/
- Grosor - /Entero de 10 dígitos/
- Año - /Entero de 10 dígitos/
- ID del glaciar (Compatible con la WGMS) - /Cadena de 20 caracteres/
- Área - /Decimal/
- Volumen - /Decimal/
- Altura - /Decimal/
- Año - /Entero de 11 dígitos/
3. *RD3*: Datos de cambio de un glaciar
- ID del glaciar (Compatible con la WGMS) - /Entero de 5 dígitos/
- Variación de área - /Entero de 10 dígitos/
- Variación de volumen - /Entero de 10 dígitos/
- Variación de grosor - /Entero de 10 dígitos/
- Año - /Entero de 10 dígitos/
4. *RD4*: Datos del administrador
- ID - /Entero de 4 dígitos/
- Fecha de alta - /Fecha en formato dd-mm-yyyy/
3. *RD3*: Datos del administrador
- ID - /Entero de 11 dígitos/
- Fecha y hora de alta - /Fecha y hora en formato yyyy-mm-dd hh:mm/
- Nombre de usuario - /Cadena de 20 caracteres máximo/
- Hash de la contraseña - /Cadena de 128 caracteres máximo/
*** Funcionales
@@ -54,7 +49,7 @@ datos relevantes para estudios acerca del cambio climático, y acotando éstos a
3. *RF3*: Cálculo de las variaciones anuales
Calcula las variaciones anuales de grosor, área y volumen para un glaciar
Calcula las variaciones anuales de altura, área y longitud para un glaciar
- Entrada: *RD2*
- Manejo: *RD3*