aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/db_handler.py26
-rw-r--r--src/map_node5_net.py43
-rw-r--r--src/static/main.js77
-rw-r--r--src/templates/options.html33
4 files changed, 134 insertions, 45 deletions
diff --git a/src/db_handler.py b/src/db_handler.py
index b438b6c..83cdad8 100644
--- a/src/db_handler.py
+++ b/src/db_handler.py
@@ -1,10 +1,12 @@
import os
+import logging
import psycopg
import yaml
+logger = logging.getLogger(__name__) # Set the logger name, to the name of the module
-class IllegalCategoryException(Exception):
+class IllegalInstructionException(Exception):
pass
@@ -23,6 +25,18 @@ with psycopg.connect(**db_con_params) as conn:
""")
categories = cur.fetchall()
categories = [category[0] for category in categories]
+ print(f"Loaded: {len(categories)} categories")
+
+ cur.execute("""
+ SELECT country
+ FROM poi
+ WHERE country is not null GROUP BY country
+ HAVING COUNT(*) > 1
+ ;""")
+ countries = cur.fetchall()
+ countries = [country[0] for country in countries]
+
+ print(f"Loaded countries: {countries}")
def get_chains() -> (list[dict]):
@@ -35,9 +49,11 @@ def get_chains() -> (list[dict]):
return chains
-def get_all(category: str) -> (list[dict]):
+def get_all(country: str, category: str) -> (list[dict]):
if category not in categories:
- raise IllegalCategoryException()
+ raise IllegalInstructionException("Category not found")
+ if country not in countries:
+ raise IllegalInstructionException("Country not found")
else:
with psycopg.connect(**db_con_params, row_factory=psycopg.rows.dict_row) as conn:
with conn.cursor() as cur:
@@ -46,6 +62,7 @@ WITH filtered AS (
SELECT osm_id, name, brand, geom, class, subclass
FROM poi
WHERE subclass = %(subclass)s
+AND country = %(country)s
)
SELECT
@@ -62,7 +79,8 @@ SELECT
FROM filtered
) polygon ON ST_Contains(polygon.geom, filtered.geom)
;
- """, {'subclass': category})
+ """, {'subclass': category, 'country': country})
all = cur.fetchall()
return all
+
diff --git a/src/map_node5_net.py b/src/map_node5_net.py
index 5bc31c3..aba576e 100644
--- a/src/map_node5_net.py
+++ b/src/map_node5_net.py
@@ -5,9 +5,44 @@ import flask
import db_handler
-app = flask.Flask(__name__, template_folder='templates', static_folder='static', static_url_path='')
+import logging
+
+
+class ColorFormatter(logging.Formatter):
+ grey = "\x1b[90;20m"
+ cyan = "\x1b[96;20m"
+ yellow = "\x1b[33;20m"
+ red = "\x1b[31;20m"
+ bold_red = "\x1b[31;1m"
+ reset = "\x1b[0m"
+ format = "%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s"
+
+ FORMATS = {
+ logging.DEBUG: grey + format + reset,
+ logging.INFO: cyan + format + reset,
+ logging.WARNING: yellow + format + reset,
+ logging.ERROR: red + format + reset,
+ logging.CRITICAL: bold_red + format + reset
+ }
+
+ def format(self, record):
+ log_fmt = self.FORMATS.get(record.levelno)
+ formatter = logging.Formatter(log_fmt)
+ return formatter.format(record)
+logger = logging.getLogger(__name__) # Instantiate a logger to be used in this module
+
+# Display every message Change this to INFO to see INFO and above (filter out DEBUG)
+# See: https://docs.python.org/3/howto/logging.html#logging-levels
+logger.root.setLevel(logging.DEBUG)
+
+stream_handler = logging.StreamHandler() # This catches and handles log messages on the root handler
+stream_handler.setFormatter(ColorFormatter())
+logger.root.addHandler(stream_handler)
+
+app = flask.Flask(__name__, template_folder='templates', static_folder='static', static_url_path='')
+
@app.route("/")
def index():
return flask.render_template('/index.html')
@@ -34,8 +69,10 @@ def categories():
@app.route("/all.json")
def all():
- category_RADIOACTIVE = flask.request.args.get('Category') # User input is RADIOACTIVE
- rows = db_handler.get_all(category_RADIOACTIVE)
+ category_RADIOACTIVE = flask.request.args.get('category') # User input is RADIOACTIVE
+ country_RADIOACTIVE = flask.request.args.get('country') # User input is RADIOACTIVE
+ #logger.debug(f'/all.json requested, input params: category: {category_RADIOACTIVE} country: {country_RADIOACTIVE}')
+ rows = db_handler.get_all(country_RADIOACTIVE, category_RADIOACTIVE)
for row in rows:
coordinates = []
for coordinate in json.loads(row['polygon'])['coordinates'][0]:
diff --git a/src/static/main.js b/src/static/main.js
index 22a9048..2aac66f 100644
--- a/src/static/main.js
+++ b/src/static/main.js
@@ -12,6 +12,7 @@ var polygons = L.featureGroup().addTo(map);
var heatmap = L.featureGroup().addTo(map);
const CategoryField = document.getElementById('Category');
+const CountryField = document.getElementById('Country');
const LoadingIndicator = document.getElementById('LoadingIndicator');
const SubmitButton = document.getElementById('SubmitButton');
const form = document.getElementById('QueryForm');
@@ -60,13 +61,13 @@ function changeCategory() {
SubmitButton.disabled = true;
LoadingIndicator.hidden = false;
const url = new URL(location);
- url.searchParams.set("Category", CategoryField.value);
+ url.searchParams.set("category", CategoryField.value);
history.pushState({}, "", url);
markers.clearLayers();
polygons.clearLayers();
- fetchAll(CategoryField.value);
+ fetchAll(CountryField.value, CategoryField.value);
}
}
@@ -75,25 +76,41 @@ function alterHeatMapParameter(event) {
}
async function fetchCategories() {
- const response = await fetch("categories.json");
- try {
- categories = await response.json();
- const CategoriesDataList = document.getElementById('CategoriesDataList');
- categories.forEach(function(item){
- var option = document.createElement('option');
- option.value = item;
- option.innerHTML = item;
- CategoriesDataList.appendChild(option);
- });
- CategoryField.pattern = categories.join('|');
- } catch (error) {
- alert(`Failed to fetch categories, are you online?\n${error.message}`)
- }
+ try {
+ const response = await fetch("categories.json");
+
+ if (!response.ok) {
+ // If the response status is not OK, throw an error
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+ CategoryField.pattern = categories.join('|');
+ } catch (error) {
+ alert(`Failed to fetch categories\n${error.message}`)
+ }
+ categories = await response.json();
+ const CategoriesDataList = document.getElementById('CategoriesDataList');
+ categories.forEach(function(item){
+ var option = document.createElement('option');
+ option.value = item;
+ option.innerHTML = item;
+ CategoriesDataList.appendChild(option);
+ });
+
+
}
async function fetchChains() {
- const response = await fetch("chains.json");
- known_store_chains = await response.json();
+ try {
+ const response = await fetch("chains.json");
+ known_store_chains = await response.json();
+ if (!response.ok) {
+ // If the response status is not OK, throw an error
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+ } catch (error) {
+ alert(`Failed to fetch chains\n${error.message}`)
+ }
+
icons["Unknown"] = L.icon({
iconUrl: 'icons/Unknown.png',
iconSize: [10, 16],
@@ -108,21 +125,21 @@ async function fetchChains() {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
- const urlParam = urlParams.get('Category')
+ const urlParam = urlParams.get('category')
const fieldValue = CategoryField.value
// Retains saved value from input field on reload
if (urlParam)
{
- fetchAll(urlParam);
+ fetchAll(CountryField.value, urlParam);
CategoryField.value = urlParam;
} else if (fieldValue !== "")
{
- fetchAll(fieldValue);
+ fetchAll(CountryField.value, fieldValue);
} else
{
CategoryField.value = DefaultSearchValue;
- fetchAll(DefaultSearchValue);
+ fetchAll(CountryField.value, DefaultSearchValue);
}
}
@@ -170,9 +187,19 @@ function drawHeatmap() {
var heat = L.heatLayer(shops_heatmap_format, {radius: parseInt(heatmap_radius_element.value)}).addTo(heatmap);
}
-async function fetchAll(Category) {
- const response = await fetch(`all.json?Category=${Category}`)
- shops = await response.json();
+async function fetchAll(country, category) {
+ let response;
+ try {
+ response = await fetch(`all.json?country=${country}&category=${category}`)
+ if (!response.ok) {
+ // If the response status is not OK, throw an error
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+ } catch (error) {
+ alert(`Failed to fetch data\n${error.message}`)
+ }
+
+ shops = await response.json();
shops.forEach((shop) => addShop(shop)); // Add icons and polygons
diff --git a/src/templates/options.html b/src/templates/options.html
index 27ef9a6..85ae8e9 100644
--- a/src/templates/options.html
+++ b/src/templates/options.html
@@ -1,18 +1,25 @@
<h1>POI map</h1>
<form id="QueryForm" disabled>
- <input class="form-control" list="CategoriesDataList" id="Category" name="Category" placeholder="Type to search..."
- aria-describedby="CategoriesHelp" value=""> {# Value set to empty string is important for JS function #}
- <datalist id="CategoriesDataList">
-
- </datalist>
- <div id="CategoriesHelp" class="form-text">Change the category shown on the map<br>
- e.g.
- <code><a href="/?Category=fast_food&HeatmapIntensity=3&HeatmapRadius=40">fast_food</a></code>,
- <code><a href="/?HeatmapIntensity=6&HeatmapRadius=100&Category=supermarket">supermarket</a></code>, or
- <code><a href="/?Category=cinema&HeatmapIntensity=25&HeatmapRadius=40">cinema</a></code>
- </div>
-
- <br>
+ <input class="form-control" list="CountriesDataList" id="Country" name="Country" placeholder="Type to search..."
+ value="Denmark"> {# Value set to empty string is important for JS function #}
+ <datalist id="CountriesDataList">
+ <option value="Denmark">Denmark</option>
+ <option value="Germany">Germany</option>
+ </datalist>
+
+ <br>
+
+ <input class="form-control" list="CategoriesDataList" id="Category" name="Category" placeholder="Type to search..."
+ aria-describedby="CategoriesHelp" value=""> {# Value set to empty string is important for JS function #}
+ <datalist id="CategoriesDataList"></datalist>
+ <div id="CategoriesHelp" class="form-text">Change the category shown on the map<br>
+ e.g.
+ <code><a href="/?Category=fast_food&HeatmapIntensity=3&HeatmapRadius=40">fast_food</a></code>,
+ <code><a href="/?HeatmapIntensity=6&HeatmapRadius=100&Category=supermarket">supermarket</a></code>, or
+ <code><a href="/?Category=cinema&HeatmapIntensity=25&HeatmapRadius=40">cinema</a></code>
+ </div>
+
+ <br>
<input class="form-control" id="HeatmapIntensity" name="HeatmapIntensity" value="5" type="number"
aria-describedby="HeatmapIntensityHelp" max=2000>