pjgerrits commited on
Commit
d740e1a
1 Parent(s): e552234

Initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ import psycopg2
3
+
4
+ app = Flask(__name__)
5
+
6
+ def connect_to_db():
7
+ return psycopg2.connect(
8
+ dbname="glprui_jloddr",
9
+ user="glprui_jloddr",
10
+ password="612ef773",
11
+ host="db.qgiscloud.com",
12
+ port="5432",
13
+ sslmode="prefer"
14
+ )
15
+
16
+ @app.route('/')
17
+ def index():
18
+ return render_template('index.html')
19
+
20
+ @app.route('/record_data', methods=['POST'])
21
+ def record_data():
22
+ data = request.json
23
+
24
+ conn = connect_to_db()
25
+ cursor = conn.cursor()
26
+
27
+ try:
28
+ cursor.execute(
29
+ """
30
+ INSERT INTO public.gettinglost_tracking (Age, Gender, Transport, TimeOfDay, DayOfWeek, Description)
31
+ VALUES (%s, %s, %s, %s, %s, %s) RETURNING ID;
32
+ """,
33
+ (data['age'], data['gender'], data['transport'], data['timeOfDay'], data['dayOfWeek'], data['description'])
34
+ )
35
+
36
+ record_id = cursor.fetchone()[0]
37
+
38
+ point_data = data['points']
39
+ for pointType, point in point_data.items():
40
+ if point:
41
+ cursor.execute(
42
+ """
43
+ INSERT INTO public.gettinglost_geom (ID, PointType, geom)
44
+ VALUES (%s, %s, ST_SetSRID(ST_Point(%s, %s), 4326));
45
+ """,
46
+ (record_id, pointType, point['lng'], point['lat'])
47
+ )
48
+
49
+ conn.commit()
50
+ return jsonify({"message": "Data recorded successfully!"})
51
+
52
+ except Exception as e:
53
+ conn.rollback()
54
+ return jsonify({"error": str(e)})
55
+
56
+ finally:
57
+ cursor.close()
58
+ conn.close()
59
+
60
+ if __name__ == '__main__':
61
+ app.run(debug=True)
gradio-interface.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+
4
+ def launch_flask():
5
+ # Run the Flask app in a subprocess
6
+ subprocess.Popen(["python", "app.py"])
7
+
8
+ # Create a Gradio interface that launches the Flask app
9
+ iface = gr.Interface(fn=launch_flask, inputs=[], outputs=[])
10
+
11
+ if __name__ == "__main__":
12
+ iface.launch()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # This file may be used to create an environment using:
2
+ # $ conda create --name <env> --file <this file>
3
+
4
+ Flask==2.1.2
5
+ Werkzeug==2.1.2
6
+ psycopg2-binary==2.9.3
7
+ gradio==2.2.15
8
+
static/Getting Lost Survey.png ADDED
templates/index.html ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Geographical Interface</title>
6
+ <meta charset="utf-8" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
9
+ <style>
10
+ #map {
11
+ width: 100%;
12
+ height: 100vh;
13
+ position: absolute;
14
+ top: 0;
15
+ left: 0;
16
+ }
17
+
18
+ #data-entry-panel {
19
+ position: absolute;
20
+ top: 10px;
21
+ right: 10px;
22
+ width: 300px;
23
+ background-color: #f8f8f8;
24
+ padding: 15px;
25
+ border: 1px solid #ddd;
26
+ border-radius: 5px;
27
+ z-index: 1000;
28
+ }
29
+
30
+ #data-entry-panel label,
31
+ #data-entry-panel select,
32
+ #data-entry-panel textarea {
33
+ display: block;
34
+ margin-bottom: 10px;
35
+ width: 100%;
36
+ }
37
+
38
+ #data-entry-form button {
39
+ display: block;
40
+ margin: 0 auto;
41
+ padding: 10px 20px;
42
+ background-color: orange;
43
+ color: white;
44
+ border: none;
45
+ border-radius: 5px;
46
+ cursor: pointer;
47
+ font-size: 16px;
48
+ }
49
+
50
+ #data-entry-form button:hover {
51
+ background-color: darkorange;
52
+ }
53
+
54
+ /* Additional styles for multi-mode transport options */
55
+ #multi-mode-options {
56
+ display: none;
57
+ margin-top: 10px;
58
+ margin-bottom: 10px;
59
+ }
60
+
61
+ /* Point Type Selector styles */
62
+ #point-selector {
63
+ position: absolute;
64
+ top: 10px;
65
+ left: 75px;
66
+ background-color: #f8f8f8;
67
+ padding: 10px;
68
+ border: 1px solid #ddd;
69
+ border-radius: 5px;
70
+ z-index: 1000;
71
+ font-size: 16px;
72
+ }
73
+ </style>
74
+ </head>
75
+
76
+ <body>
77
+ <div id="map"></div>
78
+
79
+ <!-- Point Type Selector on the Left -->
80
+ <div id="point-selector">
81
+ <p>Select Point Type:</p>
82
+ <label style="display: inline-block; margin-right: 10px;">
83
+ <input type="radio" name="pointType" value="start" checked>
84
+ <span style="color: red;">●</span> Start Point
85
+ </label>
86
+ <label style="display: inline-block; margin-right: 10px;">
87
+ <input type="radio" name="pointType" value="lost">
88
+ <span style="color: yellow;">●</span> Getting Lost Point
89
+ </label>
90
+ <label style="display: inline-block;">
91
+ <input type="radio" name="pointType" value="end">
92
+ <span style="color: blue;">●</span> End Point
93
+ </label>
94
+ </div>
95
+
96
+ <!-- Side Panel -->
97
+ <div id="data-entry-panel">
98
+ <h3>Enter Data</h3>
99
+ <form id="data-entry-form">
100
+ <label for="age">Age:</label>
101
+ <select id="age">
102
+ <option value="0-10">0-10</option>
103
+ <option value="11-20">11-20</option>
104
+ <option value="21-30">21-30</option>
105
+ <option value="31-40">31-40</option>
106
+ <option value="41-50">41-50</option>
107
+ <option value="51-60">51-60</option>
108
+ <option value="61+">61+</option>
109
+ </select>
110
+
111
+ <label for="gender">Gender:</label>
112
+ <select id="gender">
113
+ <option value="F">Female</option>
114
+ <option value="M">Male</option>
115
+ <option value="O">Other</option>
116
+ <option value="PNTS">Prefer Not To Say</option>
117
+ </select>
118
+
119
+ <label for="transport">Mode of Transport:</label>
120
+ <select id="transport" onchange="checkTransportMode()">
121
+ <option value="Walk">Walk</option>
122
+ <option value="Car">Car</option>
123
+ <option value="Bike">Bike</option>
124
+ <option value="Train">Train</option>
125
+ <option value="Subway">Subway</option>
126
+ <option value="Other">Other</option>
127
+ <option value="Multi">Multi</option>
128
+ </select>
129
+
130
+ <div id="multi-mode-options">
131
+ <p>Select modes used:</p>
132
+ <input type="checkbox" id="walk" name="mode" value="Walk">
133
+ <label for="walk">Walk</label><br>
134
+ <input type="checkbox" id="Train" name="mode" value="Train">
135
+ <label for="walk">Train</label><br>
136
+ <input type="checkbox" id="Bike" name="mode" value="Bike">
137
+ <label for="Bike">Bike</label><br>
138
+ <input type="checkbox" id="Subway" name="mode" value="Subway">
139
+ <label for="Subway">Subway</label><br>
140
+ </div>
141
+
142
+ <label for="timeOfDay">Time Of Day:</label>
143
+ <select id="timeOfDay">
144
+ <option value="Morning">Morning</option>
145
+ <option value="Afternoon">Afternoon</option>
146
+ <option value="Evening">Evening</option>
147
+ <option value="Night">Night</option>
148
+ </select>
149
+
150
+ <label for="dayOfWeek">Date (Day of the week):</label>
151
+ <select id="dayOfWeek">
152
+ <option value="Monday">Monday</option>
153
+ <option value="Tuesday">Tuesday</option>
154
+ <option value="Wednesday">Wednesday</option>
155
+ <option value="Thursday">Thursday</option>
156
+ <option value="Friday">Friday</option>
157
+ <option value="Saturday">Saturday</option>
158
+ <option value="Sunday">Sunday</option>
159
+ </select>
160
+
161
+ <label for="description">Why did you get lost?</label>
162
+ <textarea id="description" rows="4"></textarea>
163
+
164
+ <button type="button" onclick="submitData()">Save</button>
165
+ <hr style="margin-top: 15px;">
166
+
167
+ <p>For a more detailed survey, click the link or scan the QR code:</p>
168
+ <a href="https://arcg.is/1GK5jP0" target="_blank">https://arcg.is/1GK5jP0</a>
169
+ <br>
170
+ <img src="/static/Getting Lost Survey.png" alt="QR Code for Survey" style="margin-top: 10px; width: 50%;">
171
+ </div>
172
+ </form>
173
+ </div>
174
+
175
+ <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
176
+ <script>
177
+ var openStreetMapLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
178
+ attribution: '© OpenStreetMap contributors'
179
+ });
180
+
181
+ var openTopoMapLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
182
+ attribution: '© OpenTopoMap contributors'
183
+ });
184
+
185
+ var map = L.map('map', {
186
+ center: [51.505, -0.09],
187
+ zoom: 13,
188
+ layers: [openStreetMapLayer]
189
+ });
190
+
191
+ var baseMaps = {
192
+ "OpenStreetMap": openStreetMapLayer,
193
+ "OpenTopoMap": openTopoMapLayer
194
+ };
195
+
196
+ L.control.layers(baseMaps, null, { position: 'topleft' }).addTo(map);
197
+
198
+ var clickedLat = null;
199
+ var clickedLon = null;
200
+
201
+ map.on('click', function (e) {
202
+ clickedLat = e.latlng.lat;
203
+ clickedLon = e.latlng.lng;
204
+
205
+ var pointType = document.querySelector('input[name="pointType"]:checked').value;
206
+ var color;
207
+
208
+ switch (pointType) {
209
+ case 'start':
210
+ color = 'red';
211
+ break;
212
+ case 'lost':
213
+ color = 'yellow';
214
+ break;
215
+ case 'end':
216
+ color = 'blue';
217
+ break;
218
+ }
219
+
220
+ L.circleMarker([clickedLat, clickedLon], {
221
+ color: color,
222
+ radius: 5
223
+ }).addTo(map);
224
+ });
225
+
226
+ function checkTransportMode() {
227
+ var transport = document.getElementById('transport').value;
228
+ var multiOptions = document.getElementById('multi-mode-options');
229
+ if (transport === 'Multi') {
230
+ multiOptions.style.display = 'block';
231
+ } else {
232
+ multiOptions.style.display = 'none';
233
+ }
234
+ }
235
+
236
+ function submitData() {
237
+ var age = document.getElementById('age').value;
238
+ var gender = document.getElementById('gender').value;
239
+ var transport = document.getElementById('transport').value;
240
+ var timeOfDay = document.getElementById('timeOfDay').value;
241
+ var dayOfWeek = document.getElementById('dayOfWeek').value;
242
+ var description = document.getElementById('description').value;
243
+ var pointType = document.getElementById('pointType').value;
244
+
245
+ fetch('/record_data', {
246
+ method: 'POST',
247
+ headers: {
248
+ 'Content-Type': 'application/json'
249
+ },
250
+ body: JSON.stringify({
251
+ age: age,
252
+ gender: gender,
253
+ transport: transport,
254
+ timeOfDay: timeOfDay,
255
+ dayOfWeek: dayOfWeek,
256
+ description: description,
257
+ lat: clickedLat,
258
+ lon: clickedLon,
259
+ pointType: pointType
260
+ })
261
+ })
262
+ .then(response => response.json())
263
+ .then(data => {
264
+ if (data.error) {
265
+ alert('Failed to record data: ' + data.error);
266
+ } else {
267
+ alert(data.message);
268
+ }
269
+ });
270
+ }
271
+
272
+ </script>
273
+ </body>
274
+
275
+ </html>