ajout connexion Liazo

This commit is contained in:
Johan Le Baut 2022-03-23 22:54:04 +01:00
parent b05cf14bf3
commit b9df2de12a
9 changed files with 263 additions and 147 deletions

View file

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

View file

@ -0,0 +1,2 @@
from .axione import *
from .liazo import *

View file

@ -1,8 +1,86 @@
# const AXIONE_ETAT_DEPLOYE =
# "DEPLOYE"
# const AXIONE_ETAT_DEPLOIEMENT = "EN COURS DE DEPLOIEMENT"
# const AXIONE_ETAT_ABANDONNE = "ABANDONNE"
# const AXIONE_ETAT_CIBLE = "CIBLE"
# const AXIONE_ETAT_SIGNE = "SIGNE"
# const AXIONE_ETAT_RAD_DEPLOIEMENT = "RAD EN COURS DE DEPLOIEMENT"
# const AXIONE_ETAT_RACCORDABLE_DEMANDE = "RACCORDABLE DEMANDE"
from ipe_fetcher.model import AreaCoordinates, Building, FAIEligibilityStatus
from ipe_fetcher.sqlite_connector.cursor import getCursorWithSpatialite
from os.path import exists
AXIONE_ETAT_DEPLOYE = "DEPLOYE"
AXIONE_ETAT_DEPLOIEMENT = "EN COURS DE DEPLOIEMENT"
AXIONE_ETAT_ABANDONNE = "ABANDONNE"
AXIONE_ETAT_CIBLE = "CIBLE"
AXIONE_ETAT_SIGNE = "SIGNE"
AXIONE_ETAT_RAD_DEPLOIEMENT = "RAD EN COURS DE DEPLOIEMENT"
AXIONE_ETAT_RACCORDABLE_DEMANDE = "RACCORDABLE DEMANDE"
class Axione:
def __init__(self, db_axione_ipe_path: str):
self.db_axione_ipe_path = db_axione_ipe_path
# Check at least that the file exists
if not exists(self.db_axione_ipe_path):
raise ValueError(f"File {self.db_axione_ipe_path} does not exist")
def getAreaBuildings(
self, areaCoordinates: AreaCoordinates, existing_buildings: dict
) -> dict:
cur = None
# Try to get cursor on Axone database
try:
cur = getCursorWithSpatialite(self.db_axione_ipe_path)
except Exception as err:
print("Error while connecting to DB: ", err)
raise "Could not get Axione data"
# 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))
""",
areaCoordinates,
)
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))
""",
areaCoordinates,
)
if not existing_buildings:
existing_buildings = dict()
buildings = existing_buildings
for b in cur.fetchall():
etatImm = b[3]
idImm = b[2]
isEligible = etatImm == AXIONE_ETAT_DEPLOYE
aquilenetEligStatus = FAIEligibilityStatus(
isEligible=isEligible,
ftthStatus=etatImm,
reasonNotEligible=None if isEligible else "Pas encore deploye",
)
if buildings.get(idImm):
buildings[idImm]["aquilenetEligStatus"] = aquilenetEligStatus
else:
building = Building(
x=b[0],
y=b[1],
idImm=idImm,
numVoieImm=b[4],
typeVoieImm=b[5],
nomVoieImm=b[6],
aquilenetEligStatus=aquilenetEligStatus,
)
buildings[idImm] = building
return buildings
else:
raise ValueError("The requested area is too wide, please reduce it")

View file

@ -0,0 +1,41 @@
import http.client as httplib
from ipe_fetcher.model import AreaCoordinates, Building, FAIEligibilityStatus
import json
class Liazo:
def __init__(self):
self.https_conn = httplib.HTTPSConnection("vador.fdn.fr")
def getAreaBuildings(
self, center_lat: float, center_lng: float, existing_buildings: dict
) -> dict:
c = self.https_conn
req = "/souscription/gps-batiments.cgi?etape=gps_batiments&lat1=%f&lat2=%f&lon1=%f&lon2=%f" % (center_lat-0.0011, center_lat+0.0011, center_lng-0.0022, center_lng+0.0022)
req = req.replace(" ", "%20")
print("Req FDN with: ", req)
c.request("GET", req)
r = c.getresponse()
if r.status < 200 or r.status >= 300:
print("Erreur de serveur chez FDN. Merci de nous faire remonter le numéro de téléphone qui provoque cette erreur")
return
d = r.read()
c.close()
v = json.loads(d.decode("utf-8"))
if not existing_buildings:
existing_buildings = dict()
buildings = existing_buildings
for building in v:
idImm=building.get('ref')
if not buildings.get(idImm):
building = Building(
y=building.get('lat'),
x=building.get('lon'),
idImm=idImm,
numVoieImm="",
typeVoieImm="",
nomVoieImm=""
)
print("add building ", building)
buildings[idImm] = building
return buildings

View file

@ -3,7 +3,6 @@ from typing import TypedDict
class FAIEligibilityStatus(TypedDict):
isEligible: bool
ftthDeployer: str
ftthStatus: str
reasonNotEligible: str
@ -18,3 +17,10 @@ class Building(TypedDict):
aquilenetEligStatus: FAIEligibilityStatus
ffdnEligStatus: FAIEligibilityStatus
othersEligStatus: FAIEligibilityStatus
class AreaCoordinates(TypedDict):
swx: float
swy: float
nex: float
ney: float

View file

@ -0,0 +1 @@
# from .cursor import *

View file

@ -0,0 +1,8 @@
import sqlite3
def getCursorWithSpatialite(db_path: str = None) -> sqlite3.Cursor:
db = sqlite3.connect(db_path)
cur = db.cursor()
db.enable_load_extension(True)
cur.execute('SELECT load_extension("mod_spatialite")')
return cur

View file

@ -4,30 +4,37 @@ from typing import TypedDict
import configparser
import sqlite3
import os
from ipe_fetcher import Liazo,Axione
class Config(TypedDict):
dbPath: str
axione_ipe_path: str
def parseConfig() -> Config:
cfg_path = os.environ.get("CONFIG", "/etc/ftth-ipe-map/conf.ini")
cfg = configparser.ConfigParser()
with open(cfg_path, "r") as f:
cfg.read_file(f)
return {'dbPath':cfg.get("DB","path")}
cfg_path = os.environ.get("CONFIG", "/etc/ftth-ipe-map/conf.ini")
cfg = configparser.ConfigParser()
with open(cfg_path, "r") as f:
cfg.read_file(f)
return {"axione_ipe_path": cfg.get("DB", "axione_ipe_path")}
app = Flask(__name__)
cfg:Config = parseConfig()
cfg: Config = parseConfig()
axione = Axione(cfg.get("axione_ipe_path"))
liazo = Liazo()
@app.route("/", methods=["GET"])
def getMap():
return render_template("map.html")
@app.route("/eligdata", methods=["GET"])
def getEligData():
args = request.args
valid_args = True
processed_args = {}
for k in ['swx', 'swy', 'nex', 'ney']:
for k in ["swx", "swy", "nex", "ney", "centerlat", "centerlng"]:
valid_args = valid_args and k in args
if valid_args:
try:
@ -35,43 +42,14 @@ def getEligData():
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
buildings = dict()
try:
buildings = axione.getAreaBuildings(processed_args, buildings)
except ValueError as err:
print("Could not get Axione data for this area:", err)
buildings = liazo.getAreaBuildings(processed_args["centerlat"], processed_args["centerlng"], buildings)
return {"buildings": buildings}
else:
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,118 +1,120 @@
const minZoomForRequest = 17;
let markers = [];
function initMap(btn) {
// Init map position/zoom. Potentially using what's in the URL search string.
const params = new URLSearchParams(window.location.search);
let x = parseFloat(params.get('x'));
let y = parseFloat(params.get('y'));
let z = parseInt(params.get('z'));
let map = L.map('map');
if(x && y && z) {
map.setView([y, x], z);
fetchEligData(map);
displayBtn(btn);
} else {
map.setView([46.710, 3.669], 6);
// Init map position/zoom. Potentially using what's in the URL search string.
const params = new URLSearchParams(window.location.search);
let x = parseFloat(params.get('x'));
let y = parseFloat(params.get('y'));
let z = parseInt(params.get('z'));
let map = L.map('map');
if (x && y && z) {
map.setView([y, x], z);
fetchEligData(map);
displayBtn(btn);
} else {
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", () => {
/* We only want to enable the search button when we reached a sufficient zoom level */
if (btn.disabled && map.getZoom() >= minZoomForRequest) {
displayBtn(btn);
}
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", () => {
/* We only want to enable the search button when we reached a sufficient zoom level */
if (btn.disabled && map.getZoom() >= minZoomForRequest) {
displayBtn(btn);
}
if (!btn.disabled && map.getZoom() < minZoomForRequest) {
hideBtn(btn);
}
});
return map;
if (!btn.disabled && map.getZoom() < minZoomForRequest) {
hideBtn(btn);
}
});
return map;
}
function initAddrSearch(map) {
const autocompleteOptions = {
debounceTime: 300,
search: async (query) => {
if(query.length > 2) {
const mapCenter = map.getCenter();
const reqUri = `https://photon.komoot.io/api/?q=${encodeURI(query)}&lat=${mapCenter.lat}&lon=${mapCenter.lng}&limit=20&lang=fr`;
const source = await fetch(reqUri);
const data = await source.json();
return data.features;
} else {
return [];
}
},
renderResult: (res, props) => {
const p = res.properties;
if(p.name && p.postcode && p.city && p.county && res.geometry.coordinates && res.geometry.coordinates.length === 2)
return `<li ${props}>${p.name} - ${p.postcode} ${p.city}, ${p.county}</li>`;
else
return "";
},
onSubmit: async (res) => {
const searchInput = document.getElementById('search-addr-autocomplete-input');
const p = res.properties;
searchInput.value = `${p.name} - ${p.postcode} ${p.city}, ${p.county}`;
// We already filtered out the result not having strictly 2 coordinates at item display
map.setView([res.geometry.coordinates[1],res.geometry.coordinates[0]], 19);
fetchEligData(map);
}
};
const autocompleteAddr = new Autocomplete("#search-addr-autocomplete", autocompleteOptions);
return autocompleteAddr;
const autocompleteOptions = {
debounceTime: 300,
search: async (query) => {
if (query.length > 2) {
const mapCenter = map.getCenter();
const reqUri = `https://photon.komoot.io/api/?q=${encodeURI(query)}&lat=${mapCenter.lat}&lon=${mapCenter.lng}&limit=20&lang=fr`;
const source = await fetch(reqUri);
const data = await source.json();
return data.features;
} else {
return [];
}
},
renderResult: (res, props) => {
const p = res.properties;
if (p.name && p.postcode && p.city && p.county && res.geometry.coordinates && res.geometry.coordinates.length === 2)
return `<li ${props}>${p.name} - ${p.postcode} ${p.city}, ${p.county}</li>`;
else
return "";
},
onSubmit: async (res) => {
const searchInput = document.getElementById('search-addr-autocomplete-input');
const p = res.properties;
searchInput.value = `${p.name} - ${p.postcode} ${p.city}, ${p.county}`;
// We already filtered out the result not having strictly 2 coordinates at item display
map.setView([res.geometry.coordinates[1], res.geometry.coordinates[0]], 19);
fetchEligData(map);
}
};
const autocompleteAddr = new Autocomplete("#search-addr-autocomplete", autocompleteOptions);
return autocompleteAddr;
}
function updateEligData(map, eligData) {
markers.map(marker => map.removeLayer(marker));
let buildings = eligData.buildings;
markers = buildings.map(building => {
const latlng = new L.latLng(building.y, building.x);
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
});
markers.map(marker => map.removeLayer(marker));
let buildings = eligData.buildings;
console.log(buildings)
markers = Object.values(buildings).map(building => {
const latlng = new L.latLng(building.y, building.x);
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
});
}
function updateUrl(map) {
const c = map.getCenter();
history.replaceState({}, "", encodeURI(`?x=${c.lng}&y=${c.lat}&z=${map.getZoom()}`));
const c = map.getCenter();
history.replaceState({}, "", encodeURI(`?x=${c.lng}&y=${c.lat}&z=${map.getZoom()}`));
}
async function fetchEligData(map) {
const zoom = map.getZoom();
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);
updateUrl(map);
}
const zoom = map.getZoom();
if (zoom >= minZoomForRequest) {
const mc = map.getCenter();
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}&centerlat=${mc.lat}&centerlng=${mc.lng}`);
const source = await fetch(reqUri);
const eligData = await source.json();
updateEligData(map, eligData);
updateUrl(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;
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;
}
function displayBtn(btn) {
btn.disabled = false;
btn.title = "Rechercher les données d'éligibilité pour cette zone."
btn.disabled = false;
btn.title = "Rechercher les données d'éligibilité pour cette zone."
}
function hideBtn(btn) {
btn.disabled = true;
btn.title = "Veuillez zoomer plus la carte avant de lancer une recherche d'éligibilité.";
btn.disabled = true;
btn.title = "Veuillez zoomer plus la carte avant de lancer une recherche d'éligibilité.";
}