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:
parent
925d93fc58
commit
38c67ee588
4 changed files with 108 additions and 23 deletions
2
webapp/config.ini.sample
Normal file
2
webapp/config.ini.sample
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[DB]
|
||||||
|
path = /path/to/ipe.sqlite
|
|
@ -1,19 +1,77 @@
|
||||||
from flask import Flask, request, render_template
|
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():
|
def getMap():
|
||||||
return render_template("map.html")
|
return render_template("map.html")
|
||||||
|
|
||||||
@app.route("/eligdata", methods=['GET'])
|
@app.route("/eligdata", methods=["GET"])
|
||||||
def getEligData():
|
def getEligData():
|
||||||
args = request.args
|
args = request.args
|
||||||
if 'swx' in args and 'swy' in args and \
|
valid_args = True
|
||||||
'nex' in args and 'ney' in args:
|
processed_args = {}
|
||||||
return { "buildings": [
|
for k in ['swx', 'swy', 'nex', 'ney']:
|
||||||
{"x": args['swx'], "y":args['swy'], "label": "dummy elig val" },
|
valid_args = valid_args and k in args
|
||||||
{"x": args['nex'], "y":args['ney'], "label": "dummy elig val 2" }
|
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:
|
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
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
||||||
|
|
|
@ -1,9 +1,23 @@
|
||||||
|
const minZoomForRequest = 17;
|
||||||
let markers = [];
|
let markers = [];
|
||||||
function initMap() {
|
function initMap(btn) {
|
||||||
let map = L.map('map').setView([46.710, 3.669], 6);
|
let map = L.map('map').setView([46.710, 3.669], 6);
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
}).addTo(map);
|
}).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;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,24 +60,35 @@ function updateEligData(map, eligData) {
|
||||||
let buildings = eligData.buildings;
|
let buildings = eligData.buildings;
|
||||||
markers = buildings.map(building => {
|
markers = buildings.map(building => {
|
||||||
const latlng = new L.latLng(building.y, building.x);
|
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);
|
map.addLayer(marker);
|
||||||
return marker
|
return marker
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchEligData(map) {
|
async function fetchEligData(map) {
|
||||||
const bounds = map.getBounds();
|
|
||||||
const sw = bounds.getSouthWest();
|
|
||||||
const ne = bounds.getNorthEast();
|
|
||||||
const zoom = map.getZoom();
|
const zoom = map.getZoom();
|
||||||
const reqUri = encodeURI(`/eligdata?swx=${sw.lng}&swy=${sw.lat}&nex=${ne.lng}&ney=${ne.lat}`);
|
if (zoom >= minZoomForRequest) {
|
||||||
const source = await fetch(reqUri);
|
const bounds = map.getBounds();
|
||||||
const eligData = await source.json();
|
const sw = bounds.getSouthWest();
|
||||||
updateEligData(map, eligData);
|
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();
|
function initBtn() {
|
||||||
const addrSearch = initAddrSearch(map);
|
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);
|
||||||
|
|
Loading…
Reference in a new issue