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);