API par coord et amélioration UI

This commit is contained in:
Johan Le Baut 2023-01-11 15:35:12 +01:00
parent 275238fa39
commit f2b749e6d3
9 changed files with 304 additions and 133 deletions

35
webapp/coordinates.py Normal file
View file

@ -0,0 +1,35 @@
import math
from ipe_fetcher import AreaCoordinates
def check_coordinates_area(coordinates: AreaCoordinates, max_area) -> AreaCoordinates:
swx = coordinates.get('swx')
swy = coordinates.get('swy')
nex = coordinates.get('nex')
ney = coordinates.get('ney')
x_interval = abs(nex - swx)
y_interval = abs(ney - swy)
area = x_interval * y_interval
if area <= max_area:
# We are within max area, use original coordinates
return coordinates
else:
# Decrease area size to max area while keeping x y ratio
new_x_interval = x_interval * math.sqrt(max_area / (x_interval * y_interval))
new_y_interval = y_interval * math.sqrt(max_area / (x_interval * y_interval))
return AreaCoordinates(
swx=(swx + nex - new_x_interval) / 2,
swy=(swy + ney - new_y_interval) / 2,
nex=(swx + nex + new_x_interval) / 2,
ney=(swy + ney + new_y_interval) / 2,
)
def check_coordinates_args(args):
processed_args = {}
for k in ["swx", "swy", "nex", "ney"]:
if k not in args:
raise ValueError(f"{k} not in args")
processed_args[k] = float(args[k])
return processed_args

View file

@ -1,9 +1,8 @@
from flask import Flask, request
from ipe_fetcher import Axione
from eligibility_api.elig_api_exceptions import ApiParamException
from coordinates import check_coordinates_args, check_coordinates_area
from ipe_fetcher.axione import AXIONE_MAX_AREA, Axione
class EligibilityApiRoutes:
def __init__(self, flask_app: Flask, axione_ipe: Axione):
@ -19,3 +18,13 @@ class EligibilityApiRoutes:
raise ApiParamException("You need to specify path parameter 'refimmeuble'")
return self.axione_ipe.get_eligibilite_per_id_immeuble(refimmeuble)
@self.flask_app.route("/eligibilite/axione/coord", methods=["GET"])
def get_axione_eligibility_per_coordinates():
args = request.args
try:
processed_args = check_coordinates_args(args)
coordinates = check_coordinates_area(processed_args, AXIONE_MAX_AREA)
return self.axione_ipe.getAreaBuildings(coordinates, {})
except ValueError:
raise ApiParamException("You need to specify path parameters 'swx' 'swy' 'nex' 'ney'")

View file

@ -16,13 +16,13 @@ class Arcep:
@staticmethod
def _get_etat_priority(etat_imm):
if etat_imm == ARCEP_ETAT_DEPLOYE:
return 0
return 10
elif etat_imm == "en cours de deploiement":
return 1
return 11
elif etat_imm != "abandonne":
return 3
return 30
else:
return 4
return 31
def getAreaBuildings(

View file

@ -15,6 +15,9 @@ AXIONE_ETAT_RACCORDABLE_DEMANDE = "RACCORDABLE DEMANDE"
AXIONE_REFIMM_TABLE_NAME = "refimm"
AXIONE_MAX_AREA = 0.08
class Axione:
def __init__(self, db_axione_ipe_path: str, db_name: str):
self.db_axione_ipe_path = db_axione_ipe_path
@ -44,9 +47,9 @@ class Axione:
elif etat_imm == AXIONE_ETAT_RAD_DEPLOIEMENT:
return 3
elif etat_imm != AXIONE_ETAT_ABANDONNE:
return 4
return 20
else:
return 5
return 21
def getAreaBuildings(
@ -68,7 +71,7 @@ class Axione:
areaCoordinates,
)
req_area = cur.fetchone()[0]
if req_area <= 0.08:
if req_area <= AXIONE_MAX_AREA:
cur.execute(
f"""
SELECT
@ -105,12 +108,14 @@ class Axione:
date_commandable = ""
# C'est bien déployé, cependant ce n'est pas encore commandable (donc bientôt et on a la date)
# On laisse isEligible = True, côté JS il faut regarder le statut pour laj l'affichage en conséquence
if isEligible and date_debut:
try:
date_formatted = datetime.strptime(date_debut, '%Y%m%d').date()
if date_formatted >= datetime.now().date():
etatImm = AXIONE_ETAT_DEPLOYE_NON_COMMANDABLE
date_commandable = date_formatted.strftime('%d/%m/%Y')
date_commandable = date_formatted.strftime('%d/%m/%Y')
except ValueError as err:
print("Error while mainpulating DateDebutAcceptationCmdAcces from Axione DB: ", err)

View file

@ -1,20 +1,29 @@
import http.client as httplib
from ipe_fetcher.model import AreaCoordinates, Building, FAIEligibilityStatus
import json
import time
import traceback
class Liazo:
def __init__(self):
self.https_conn = httplib.HTTPSConnection("vador.fdn.fr")
pass
def getAreaBuildings(
self, narrow_coordinates: AreaCoordinates(), existing_buildings: dict
) -> dict:
nc=narrow_coordinates
c = self.https_conn
req = "/souscription/gps-batiments.cgi?etape=gps_batiments&lat1=%f&lat2=%f&lon1=%f&lon2=%f" % (nc['swy'],nc['ney'],nc['swx'],nc['nex'])
c = httplib.HTTPSConnection("vador.fdn.fr")
api_params = "etape=gps_batiments&lat1=%f&lat2=%f&lon1=%f&lon2=%f" % (nc['swy'], nc['ney'], nc['swx'], nc['nex'])
req = f"/souscription/gps-batiments.cgi?{api_params}"
req = req.replace(" ", "%20")
c.request("GET", req)
r = c.getresponse()
r = None
try:
c.request("GET", req)
r = c.getresponse()
except Exception:
print(f"Could not call Liazo API to get Buildings, params: {api_params}")
print(traceback.format_exc())
return existing_buildings
if r.status < 200 or r.status >= 300:
print("Erreur de serveur chez FDN.")
return existing_buildings
@ -27,7 +36,7 @@ class Liazo:
for building in v:
fdnEligStatus = FAIEligibilityStatus(
isEligible=True,
ftthStatus="DEPLOYE", # Pas de status donc on dit que c'est ok mais on check avec l'arcep si axione KO cote front
ftthStatus="DEPLOYE", # Pas de status donc on dit que c'est ok mais on check avec l'arcep si axione KO cote front
reasonNotEligible=None,
)
idImm=building.get('ref')
@ -49,7 +58,7 @@ class Liazo:
commune="",
bat_info="",
found_in = ["liazo"],
etat_imm_priority=1,
etat_imm_priority=4,
fdnEligStatus=fdnEligStatus,
aquilenetEligStatus=FAIEligibilityStatus(isEligible=False, reasonNotEligible="", ftthStatus=""),
othersEligStatus=FAIEligibilityStatus(isEligible=False, reasonNotEligible="", ftthStatus=""),

View file

@ -7,7 +7,11 @@ from flask import Flask, request, render_template, redirect
from eligibility_api.elig_api_exceptions import FlaskExceptions
from eligibility_api.elig_api_routes import EligibilityApiRoutes
from ipe_fetcher import Liazo, Axione, Arcep, AreaCoordinates
from coordinates import check_coordinates_area, check_coordinates_args
LIAZO_MAX_X_INTERVAL = 0.0022
LIAZO_MAX_Y_INTERVAL = 0.0011
LIAZO_MAX_AREA = LIAZO_MAX_X_INTERVAL * LIAZO_MAX_Y_INTERVAL
class Config(TypedDict):
axione_ipe_path: str
@ -50,43 +54,38 @@ def getEligData():
args = request.args
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
try:
processed_args = check_coordinates_args(args)
except ValueError:
valid_args = False
if valid_args:
# Need to narrow coordinates for Liazo API call
coordinates = check_coordinates_area(processed_args, LIAZO_MAX_AREA)
# computes center
centerx = (processed_args['swx'] + processed_args['nex']) / 2
centery = (processed_args['swy'] + processed_args['ney']) / 2
narrow_x = 0.0022
narrow_y = 0.0011
narrow_coordinates = AreaCoordinates(
swx=centerx - narrow_x,
swy=centery - narrow_y,
nex=centerx + narrow_x,
ney=centery + narrow_y,
)
buildings = dict()
try:
buildings = arcep.getAreaBuildings(narrow_coordinates, buildings)
buildings = axione.getAreaBuildings(narrow_coordinates, buildings)
buildings = arcep.getAreaBuildings(coordinates, buildings)
buildings = axione.getAreaBuildings(coordinates, buildings)
except ValueError as err:
print("Could not get Axione data for this area:", err)
buildings = liazo.getAreaBuildings(narrow_coordinates, buildings)
sorted_buildings = sorted(buildings.values(), key=lambda d: d.get('etat_imm_priority', 1), reverse=True)
buildings = liazo.getAreaBuildings(coordinates, buildings)
return {"buildings": list(sorted_buildings)}
return {"buildings": list(buildings.values())}
else:
return "Invalid bounding box coordinates", 400
@app.route("/eligdata/bounds", methods=["GET"])
def getEligDataBounds():
args = request.args
try:
processed_args = check_coordinates_args(args)
return {"bounds": check_coordinates_area(processed_args, LIAZO_MAX_AREA)}
except ValueError:
return "Invalid bounding box coordinates", 400
@app.route("/eligtest/ftth", methods=["GET"])
def testFtth():
args = request.args

View file

@ -15,8 +15,8 @@ body {
}
#btn-load-elig-data {
top: 4em;
right: 0;
top: 10em;
left: 1em;
position: fixed;
z-index: 1;
padding: .5em;
@ -37,4 +37,24 @@ body {
.nonDeployee {
display: inline;
color: brown;
}
.loader {
width: 48px;
height: 48px;
border: 5px solid #1787c2;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -1,4 +1,4 @@
const minZoomForRequest = 17;
const minZoomForRequest = 16;
const urlADSL = 'https://tools.aquilenet.fr/cgi-bin/recherchend.cgi'
const urlTestFTTH = 'https://tools.aquilenet.fr/cgi-bin/test.cgi'
@ -71,7 +71,30 @@ streetTypeConversion.set("zone d'aménagement différé", "zad")
streetTypeConversion.set("zone industrielle", "zi")
streetTypeConversion.set("zone", "zone")
let markers = [];
let markers = new Map();
// Default search bounds
DEFAULT_MAX_LNG_INTERVAL = 0.0028
DEFAULT_MAX_LAT_INTERVAL = 0.0014
// Search bounds from server
server_max_lng_interval = undefined
server_max_lat_interval = undefined
function getRectangleCoord(map) {
max_lng_interval = DEFAULT_MAX_LNG_INTERVAL
max_lat_interval = DEFAULT_MAX_LAT_INTERVAL
if (server_max_lat_interval !== undefined && server_max_lng_interval !== undefined) {
max_lng_interval = server_max_lng_interval
max_lat_interval = server_max_lat_interval
}
let center = map.getCenter();
let corner1 = L.latLng(center.lat - (max_lat_interval / 2), center.lng - (max_lng_interval / 2));
let corner2 = L.latLng(center.lat + (max_lat_interval / 2), center.lng + (max_lng_interval / 2));
return [corner1, corner2]
}
function initMap(btn) {
// Init map position/zoom. Potentially using what's in the URL search string.
@ -91,7 +114,8 @@ function initMap(btn) {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
map.on("zoom", () => {
map.on("zoom move", () => {
/* We only want to enable the search button when we reached a sufficient zoom level */
if (btn.disabled && map.getZoom() >= minZoomForRequest) {
displayBtn(btn);
@ -100,9 +124,52 @@ function initMap(btn) {
hideBtn(btn);
}
});
map.on("zoomend moveend", () => {
if (map.getZoom() >= minZoomForRequest) {
fetchEligData(map);
}
});
return map;
}
async function initLimitsBox(map, btn) {
// Create box to show where data is fetched
const box = createRectangleBox(map);
await getServerBoxBounds(map, box);
box.addTo(map);
map.on("zoom move", () => {
box.setBounds(getRectangleCoord(map))
})
btn.addEventListener("click", () => {
getServerBoxBounds(map, box)
});
}
function createRectangleBox(map) {
return L.rectangle(getRectangleCoord(map), {color: "#ff7800", fillOpacity: 0.07, weight: 1});
}
// Ask server the narrowed area bounds that it will search in
async function getServerBoxBounds(map, box) {
const bounds = map.getBounds();
const sw = bounds.getSouthWest();
const ne = bounds.getNorthEast();
const reqUri = encodeURI(`eligdata/bounds?swx=${sw.lng}&swy=${sw.lat}&nex=${ne.lng}&ney=${ne.lat}`);
const resp = await fetch(reqUri);
if (resp.status != 200) {
return
}
const data = await resp.json();
server_max_lat_interval = data.bounds.ney - data.bounds.swy
server_max_lng_interval = data.bounds.nex - data.bounds.swx
box.setBounds(getRectangleCoord(map))
}
function initAddrSearch(map) {
const autocompleteOptions = {
debounceTime: 300,
@ -138,84 +205,84 @@ function initAddrSearch(map) {
}
function updateEligData(map, eligData) {
if (markers) {
markers.map(marker => map.removeLayer(marker));
}
let buildings = eligData.buildings;
markers = buildings.forEach(building => {
const latlng = new L.latLng(building.y, building.x);
let addrImm = `${building.numVoieImm} ${building.typeVoieImm} ${building.nomVoieImm}`
if (building.bat_info != "") {
addrImm += ` (Bat ${building.bat_info})`
}
let colorMarker = 'black'
let messageElig = ``
eligTestApi = `eligtest/ftth?idImm=${building.idImm}&codePostal=${building.codePostal}&axione=${building.aquilenetEligStatus.isEligible}&liazo=${building.fdnEligStatus.isEligible}`
// éligible chez Aquilenet, lien pour le test
if (building.aquilenetEligStatus.isEligible) {
buildings.forEach(building => {
if (! markers.has(building.idImm)) {
const latlng = new L.latLng(building.y, building.x);
let addrImm = `${building.numVoieImm} ${building.typeVoieImm} ${building.nomVoieImm}`
if (building.bat_info != "") {
addrImm += ` (Bat ${building.bat_info})`
}
let colorMarker = 'black'
let messageElig = ``
eligTestApi = `eligtest/ftth?idImm=${building.idImm}&codePostal=${building.codePostal}&axione=${building.aquilenetEligStatus.isEligible}&liazo=${building.fdnEligStatus.isEligible}`
// éligible chez Aquilenet, lien pour le test
if (building.aquilenetEligStatus.isEligible) {
// Si fibre Axione déployé mais pas encore commandable
if (building.aquilenetEligStatus.ftthStatus == "DEPLOYE MAIS NON COMMANDABLE") {
colorMarker = 'orange'
messageElig = `<p class=deployeeAquilenet>Fibre deployée mais ne sera commandable qu\'à partir du ${building.aquilenetEligStatus.dateCommandable}</p>`
} else {
messageElig = `<p class=deployeeAquilenet>Fibre deployée et disponible par Aquilenet !</p>`
const zip = encodeURIComponent(building.codePostal);
const idImm = encodeURIComponent(building.idImm);
messageElig += `<br/><a href=${urlTestFTTH}?ftth=1&axione=1&adsltel=NOUVEAU&cp=${zip}&refimmeuble=${idImm}` +
`>Tester l'éligibilité</a>`
colorMarker = 'green'
}
// Si fibre Axione déployé mais pas encore commandable
if (building.aquilenetEligStatus.ftthStatus == "DEPLOYE MAIS NON COMMANDABLE") {
colorMarker = 'orange'
messageElig = `<p class=deployeeAquilenet>Fibre deployée mais ne sera commandable qu\'à partir du ${building.aquilenetEligStatus.dateCommandable}</p>`
} else {
messageElig = `<p class=deployeeAquilenet>Fibre deployée et disponible par Aquilenet !</p>`
const zip = encodeURIComponent(building.codePostal);
const idImm = encodeURIComponent(building.idImm);
messageElig += `<br/><a href=${urlTestFTTH}?ftth=1&axione=1&adsltel=NOUVEAU&cp=${zip}&refimmeuble=${idImm}` +
`>Tester l'éligibilité</a>`
colorMarker = 'green'
}
// pas de données Axione mais Kosc nous renvoie qque chose à cette adresse (fdnEligStatus)
// c'est peut être OK, on croise avec les données ARCEP (othersEligStatus)
// Enfin on affiche un lien vers le test d'éligibilté KOSC à cette adresse
} else if (building.fdnEligStatus.isEligible && building.othersEligStatus.isEligible) {
messageElig = `<p class=deployeeFDN>Fibre deployee mais pas chez Axione !`
messageElig += `<br/><a href=${eligTestApi}>Tester l'eligibilite par Kosc et Bouygues</a></p>`
colorMarker = 'orange'
// Pas de données Kosc ou Axione mais l'ARCEP nous dit qu'une fibre est déployée à cette adresse
} else if (building.othersEligStatus.isEligible) {
messageElig = `<p class=deployeeAutres>Fibre deployee mais non eligible Aquilenet, desole :(</p>`
colorMarker = 'red'
// Pas de fibre il semblerait, proposer un test ADSL Aquilenet
} else {
messageElig = `<p class=nonDeployee>Fibre non deployee :(</p>`
const zip = encodeURIComponent(building.codePostal);
const comm = encodeURIComponent(building.commune);
let convertType = streetTypeConversion.get(building.typeVoieImm.toLowerCase());
if (!convertType) {
convertType = building.typeVoieImm;
// pas de données Axione mais Kosc nous renvoie qque chose à cette adresse (fdnEligStatus)
// c'est peut être OK, on croise avec les données ARCEP (othersEligStatus)
// Enfin on affiche un lien vers le test d'éligibilté KOSC à cette adresse
} else if (building.fdnEligStatus.isEligible && building.othersEligStatus.isEligible) {
messageElig = `<p class=deployeeFDN>Fibre deployee mais pas chez Axione !`
messageElig += `<br/><a href=${eligTestApi}>Tester l'eligibilite par Kosc et Bouygues</a></p>`
colorMarker = 'orange'
// Pas de données Kosc ou Axione mais l'ARCEP nous dit qu'une fibre est déployée à cette adresse
} else if (building.othersEligStatus.isEligible) {
messageElig = `<p class=deployeeAutres>Fibre deployee mais non eligible Aquilenet, desole :(</p>`
colorMarker = 'red'
// Pas de fibre il semblerait, proposer un test ADSL Aquilenet
} else {
messageElig = `<p class=nonDeployee>Fibre non deployee :(</p>`
const zip = encodeURIComponent(building.codePostal);
const comm = encodeURIComponent(building.commune);
let convertType = streetTypeConversion.get(building.typeVoieImm.toLowerCase());
if (!convertType) {
convertType = building.typeVoieImm;
}
const street = encodeURIComponent(`${convertType} ${building.nomVoieImm}`)
const street_nb = encodeURIComponent(building.numVoieImm)
messageElig += `<br/><a href=${urlADSL}?zip=${zip}&city=${comm}&street=${street}&street_nb=${street_nb}&gps=&do=1&submit=Valider` +
`>Tester ADSL a cette adresse</a>`
if (building.othersEligStatus.reasonNotEligible != "") {
messageElig += `<br/><br/>Status general ARCEP: ${building.othersEligStatus.reasonNotEligible}`
}
}
const street = encodeURIComponent(`${convertType} ${building.nomVoieImm}`)
const street_nb = encodeURIComponent(building.numVoieImm)
messageElig += `<br/><a href=${urlADSL}?zip=${zip}&city=${comm}&street=${street}&street_nb=${street_nb}&gps=&do=1&submit=Valider` +
`>Tester ADSL a cette adresse</a>`
if (building.othersEligStatus.reasonNotEligible != "") {
messageElig += `<br/><br/>Status general ARCEP: ${building.othersEligStatus.reasonNotEligible}`
// Si pas d'éligibilité fibre, on affiche la raison si elle existe
if (building.aquilenetEligStatus.reasonNotEligible != "") {
messageElig += `<br/> Pour Aquilenet, raison non eligible: ${building.aquilenetEligStatus.reasonNotEligible}`
}
var markerIcon = new L.Icon({
iconUrl: `static/icons/marker-icon-${colorMarker}.png`,
shadowUrl: 'static/vendor/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
const marker = new L.marker(latlng, {
icon: markerIcon,
zIndexOffset: - building.etat_imm_priority
})
.bindPopup(`${addrImm}<br/>${building.codePostal} ${building.commune}` +
`<br/><br/>${messageElig}<br/><br/>Ref Immeuble: ${building.idImm}`, {
maxWidth: 560
});
map.addLayer(marker);
markers.set(building.idImm, marker)
}
// Si pas d'éligibilité fibre, on affiche la raison si elle existe
if (building.aquilenetEligStatus.reasonNotEligible != "") {
messageElig += `<br/> Pour Aquilenet, raison non eligible: ${building.aquilenetEligStatus.reasonNotEligible}`
}
var markerIcon = new L.Icon({
iconUrl: `static/icons/marker-icon-${colorMarker}.png`,
shadowUrl: 'static/vendor/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
const marker = new L.marker(latlng, {
icon: markerIcon
})
.bindPopup(`${addrImm}<br/>${building.codePostal} ${building.commune}` +
`<br/><br/>${messageElig}<br/><br/>Ref Immeuble: ${building.idImm}`, {
maxWidth: 560
});
map.addLayer(marker);
return marker
});
}
@ -230,12 +297,17 @@ async function fetchEligData(map) {
const bounds = map.getBounds();
const sw = bounds.getSouthWest();
const ne = bounds.getNorthEast();
const btn = document.getElementById("btn-load-elig-data");
let btn = document.getElementById("btn-load-elig-data");
waitBtn(btn);
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 resp = await fetch(reqUri);
if (resp.status == 200) {
const eligData = await resp.json();
updateEligData(map, eligData);
} else {
error = await resp.text()
console.log(`Error could not get data from server: ${resp.status} ${error}`)
}
updateUrl(map);
displayBtn(btn);
}
@ -244,27 +316,50 @@ async function fetchEligData(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);
btn.title = "Veuillez zoomer plus la carte pour charger l'éligibilité.";
return btn;
}
function setBtnListener(btn, map) {
btn.onclick = () => {
// Reset markers when button is clicked
if (markers) {
for (let marker of markers.values()){
map.removeLayer(marker);
}
markers.clear();
}
fetchEligData(map);
}
}
function displayBtn(btn) {
btn.classList.remove('loader');
btn.disabled = false;
btn.title = "Rechercher les données d'éligibilité pour cette zone."
btn.title = "Actualiser la recherche dans cette zone"
btn.innerHTML = "Actualiser";
}
function hideBtn(btn) {
btn.disabled = true;
btn.title = "Veuillez zoomer plus la carte avant de lancer une recherche d'éligibilité.";
btn.innerHTML = "Zoomez sur la carte";
btn.title = "Veuillez zoomer plus la carte afin de lancer la recherche d'éligibilité.";
}
function waitBtn(btn) {
btn.disabled = true;
btn.innerHTML = "";
btn.title = "Chargement des batiments...";
btn.classList.add('loader');
}
// Init button and map
const btn = initBtn();
const map = initMap(btn);
const addrSearch = initAddrSearch(map);
const addrSearch = initAddrSearch(map);
setBtnListener(btn, map);
// Init a limits box that shows area where data will be fetched
initLimitsBox(map, btn);

View file

@ -18,14 +18,13 @@
</head>
<body>
<button id="btn-load-elig-data" type="button" disabled>Zoomez sur la carte</button>
<div class="autocomplete" id="search-addr-autocomplete">
<input id="search-addr-autocomplete-input" class="autocomplete-input"
spellcheck="false" autocorrect="off"t e autocomplete="off"
autocapitalize="off" placeholder="Votre Adresse"/>
<ul class="autocomplete-result-list"/>
</div>
<button id="btn-load-elig-data" type="button">Récupérer les données d'éligibilité
pour cette zone</button>
<div id="map"/>
<script>
document.addEventListener("DOMContentLoaded", function(event) {