From 38c67ee588c0b134a3856963a14a226068b87d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Baylac-Jacqu=C3=A9?= Date: Tue, 22 Feb 2022 12:48:50 +0100 Subject: [PATCH] Webapp: add real elig data query When a acceptable zoom level is reached, the frontend is going to query the DB for the eligibility data associated to the region the user is viewing. When requesting too much data, the frontend gets pretty slow. In order to prevent that, we require a minimal zoom level before triggering the actual request. --- webapp/config.ini.sample | 2 ++ webapp/main.py | 78 ++++++++++++++++++++++++++++++++++------ webapp/run-dev-server | 2 +- webapp/templates/app.js | 49 ++++++++++++++++++------- 4 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 webapp/config.ini.sample diff --git a/webapp/config.ini.sample b/webapp/config.ini.sample new file mode 100644 index 0000000..2e3a82b --- /dev/null +++ b/webapp/config.ini.sample @@ -0,0 +1,2 @@ +[DB] + path = /path/to/ipe.sqlite \ No newline at end of file diff --git a/webapp/main.py b/webapp/main.py index 1d7efd5..85123cb 100644 --- a/webapp/main.py +++ b/webapp/main.py @@ -1,19 +1,77 @@ from flask import Flask, request, render_template -app = Flask(__name__) +from typing import TypedDict +import configparser +import sqlite3 +import os -@app.route("/", methods=['GET']) +class Config(TypedDict): + dbPath: str + +def parseConfig() -> Config: + cfg_path = os.environ.get("CONFIG", "/etc/ipe-ftth-elig/conf.ini") + cfg = configparser.ConfigParser() + with open(cfg_path, "r") as f: + cfg.read_file(f) + return {'dbPath':cfg.get("DB","path")} + +app = Flask(__name__) +cfg:Config = parseConfig() + +@app.route("/", methods=["GET"]) def getMap(): return render_template("map.html") -@app.route("/eligdata", methods=['GET']) +@app.route("/eligdata", methods=["GET"]) def getEligData(): args = request.args - if 'swx' in args and 'swy' in args and \ - 'nex' in args and 'ney' in args: - return { "buildings": [ - {"x": args['swx'], "y":args['swy'], "label": "dummy elig val" }, - {"x": args['nex'], "y":args['ney'], "label": "dummy elig val 2" } - ]} + valid_args = True + processed_args = {} + for k in ['swx', 'swy', 'nex', 'ney']: + valid_args = valid_args and k in args + if valid_args: + try: + processed_args[k] = float(args[k]) + except ValueError: + valid_args = False + if valid_args: + cur = cursorWithSpatialite() + # Let's first see how big is the area we're about to query. + # If it's too big, abort the request to prevent a server DOS. + cur.execute(''' + SELECT Area(BuildMBR(:swx,:swy,:nex,:ney,4326)) + ''',processed_args) + req_area = cur.fetchone()[0] + if req_area <= 0.08: + cur.execute(''' + SELECT + X(ImmeubleGeoPoint), + Y(ImmeubleGeoPoint), + IdentifiantImmeuble, + EtatImmeuble, + NumeroVoieImmeuble, + TypeVoieImmeuble, + NomVoieImmeuble + FROM ipe + WHERE ROWID IN ( + SELECT ROWID FROM SpatialIndex + WHERE f_table_name = 'ipe' AND + search_frame = BuildMBR(:swx, :swy, :nex, :ney, 4326)) + ''',processed_args) + buildings = [ { + 'x':b[0], 'y':b[1], 'idImm':b[2], + 'etatImm':b[3], 'numVoieImm': b[4], + 'typeVoieImm': b[5], 'nomVoieImm': b[6] + } for b in cur.fetchall()] + return { "buildings": buildings} + else: + return "The requested area is too wide, please reduce it", 400 else: - return "Missing coordinates", 400 + return "Invalid bounding box coordinates", 400 + +def cursorWithSpatialite(): + db = sqlite3.connect(cfg['dbPath']) + cur = db.cursor() + db.enable_load_extension(True) + cur.execute('SELECT load_extension("mod_spatialite")') + return cur diff --git a/webapp/run-dev-server b/webapp/run-dev-server index 2439d28..4438644 100755 --- a/webapp/run-dev-server +++ b/webapp/run-dev-server @@ -1,3 +1,3 @@ #!/usr/bin/env bash -TEMPLATES_AUTO_RELOAD=true FLASK_ENV=development FLASK_APP=main poetry run flask run --reload +CONFIG=./config.ini TEMPLATES_AUTO_RELOAD=true FLASK_ENV=development FLASK_APP=main poetry run flask run --reload diff --git a/webapp/templates/app.js b/webapp/templates/app.js index 43bb25f..c21977d 100644 --- a/webapp/templates/app.js +++ b/webapp/templates/app.js @@ -1,9 +1,23 @@ +const minZoomForRequest = 17; let markers = []; -function initMap() { +function initMap(btn) { let map = L.map('map').setView([46.710, 3.669], 6); L.tileLayer('https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); + map.on("zoom", () => { + + console.log(map.getZoom()); + /* We only want to enable the search button when we reached a sufficient zoom level */ + if (btn.disabled && map.getZoom() >= minZoomForRequest) { + btn.disabled = false; + btn.title = "Rechercher les données d'éligibilité pour cette zone." + } + if (!btn.disabled && map.getZoom() < minZoomForRequest) { + btn.disabled = true; + btn.title = "Veuillez zoomer plus la carte avant de lancer une recherche d'éligibilité."; + } + }); return map; } @@ -46,24 +60,35 @@ function updateEligData(map, eligData) { let buildings = eligData.buildings; markers = buildings.map(building => { const latlng = new L.latLng(building.y, building.x); - const marker = new L.marker(latlng).bindPopup(building.label); + const addrImm = `${building.numVoieImm} ${building.typeVoieImm} ${building.nomVoieImm}` + const marker = new L.marker(latlng) + .bindPopup(`${addrImm}
Etat: ${building.etatImm}
Code Immeuble: ${building.idImm}`); map.addLayer(marker); return marker }); } async function fetchEligData(map) { - const bounds = map.getBounds(); - const sw = bounds.getSouthWest(); - const ne = bounds.getNorthEast(); const zoom = map.getZoom(); - const reqUri = encodeURI(`/eligdata?swx=${sw.lng}&swy=${sw.lat}&nex=${ne.lng}&ney=${ne.lat}`); - const source = await fetch(reqUri); - const eligData = await source.json(); - updateEligData(map, eligData); + if (zoom >= minZoomForRequest) { + const bounds = map.getBounds(); + const sw = bounds.getSouthWest(); + const ne = bounds.getNorthEast(); + const reqUri = encodeURI(`/eligdata?swx=${sw.lng}&swy=${sw.lat}&nex=${ne.lng}&ney=${ne.lat}`); + const source = await fetch(reqUri); + const eligData = await source.json(); + updateEligData(map, eligData); + } } -const map = initMap(); -const addrSearch = initAddrSearch(map); +function initBtn() { + const btn = document.getElementById("btn-load-elig-data"); + btn.disabled = true; + btn.title = "Veuillez zoomer plus la carte avant de lancer une recherche d'éligibilité."; + btn.onclick = () => fetchEligData(map); + return btn; +} -document.getElementById("btn-load-elig-data").onclick = () => fetchEligData(map); +const btn = initBtn(); +const map = initMap(btn); +const addrSearch = initAddrSearch(map);