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.
This commit is contained in:
Félix Baylac-Jacqué 2022-02-22 12:48:50 +01:00
parent 925d93fc58
commit 38c67ee588
4 changed files with 108 additions and 23 deletions

2
webapp/config.ini.sample Normal file
View File

@ -0,0 +1,2 @@
[DB]
path = /path/to/ipe.sqlite

View File

@ -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

View File

@ -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

View File

@ -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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> 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}<br/>Etat: ${building.etatImm}<br/>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);