diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db_handler.py | 26 | ||||
| -rw-r--r-- | src/map_node5_net.py | 43 | ||||
| -rw-r--r-- | src/static/main.js | 77 | ||||
| -rw-r--r-- | src/templates/options.html | 33 |
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> |
