You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
13 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Dépendances
from scrapy import signals
# Python a une fonctionnalité regex native (re.py) mais elle a quelques limitations qui dans le cas présent rendent les regex inopérants
# On importe donc regex.py (pip install regex) qui étend les capacités de re.py (notamment la tolérance aux erreurs)
import regex
class SplitAndSort:
@classmethod
# Une fonction qui sépare les adresses selon les noms de rue
def splitStreet(self, value):
# Le regex qui évalue là où il faut séparer la chaîne de caractère
# (présence de "et / - + ainsi que" )
# (sans que ce séparateur ne soit placé près d'un groupe de numéros de la même rue)
expr = r"((?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))(|\bet)|(?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))\/|(ainsi\sque)|((?<!(\d))\s-\s(?!(bt)|(bis)|(ter)))|(?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))\+)"
# Remplacement de chaque séparateur par un pipe ("|")
subst = "|"
repl = regex.sub(expr, subst, value, 0, regex.MULTILINE | regex.IGNORECASE)
# Formattage (supression des espaces insécables et des ":")
filtered = regex.sub('\\xa0|:', '', repl, 0, regex.MULTILINE | regex.IGNORECASE)
# Séparation en liste
splitted = regex.split('\|', filtered, regex.MULTILINE | regex.IGNORECASE)
# Suppression des espaces en début et fin de chaîne
stripped = [x.strip() for x in splitted]
# Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
result = list(filter(None, stripped))
return result
# Une fonction qui valide tout ce qui est un couple n°/nom de rue
def isWorth(self, value):
# Le regex qui évalue ce qu'il faut garder
expr = r"(?=.*[a-z])(?=.*\d+).*"
# Un regex contre lequel comparer avant pour supprimer les exceptions
# (cas d'usage : "jardin public du 19 mars 1962" est reconnu comme un couple n°/nom de rue)
# ce n'est pas très sexy, il vaudrait mieux un regex plus strict,
# mais il serait trop complexe pour que ça en vaille la peine pour de rares exceptions
# (possibilité d'en ajouter d'autres ici par la suite si besoin)
xcp = r"(jardin public)|(Bâtiment 12)"
# Le résultat (booléen) si le regex match ou non, après comparaison avec les exceptions
if bool(regex.findall(xcp, value, regex.MULTILINE | regex.IGNORECASE)) is False :
result =bool(regex.findall(expr, value, regex.MULTILINE | regex.IGNORECASE))
else :
result = False
return result
# Une fonction qui enlève tous les trucs peu utiles ou encombrants des adresses
# ("immeuble", infos en parenthèses, infos sur l'arrêté, etc)
def removeClutter(self,value):
# Le regex qui évalue ce qu'il faut retirer
expr = r"\(.*\)|Immeuble|Chapelle,|Arrêté.*|\((.*){0,3}|"
# Le résultat après retrait
result = regex.sub(expr, '', value, regex.IGNORECASE | regex.MULTILINE)
stripped = result.strip()
return stripped
# Une fonction qui sépare les numéros des adresses
def separateNbr(self,value):
# Le regex qui évalue ce qui est un numéro ou un groupe de numéros
expr = r"(((\d).*(\da\b))(?=\s?\S?.{8,})|((\d).*(\db\b))(?=\s?\S?.{8,})|((\d).*(\dt\b))(?=\s?\S?.{8,})|((\d).*(\dbis\b))(?=\s?\S?.{8,})|((\d).*(\dter\b))(?=\s?\S?.{8,})|((\d).*(\d))(?=\s?\S?.{8,})|(\da\b)(?=\s?\S?.{8,})|(\db\b)(?=\s?\S?.{8,})|(\dbis\b)(?=\s?\S?.{8,})|(\dt\b)(?=\s?\S?.{8,})|(\dter\b)(?=\s?\S?.{8,})|(\d)(?=\s?\S?.{8,}))"
# On extrait le groupe numérique et on le place dans la variable "nbr"
sep = regex.search(expr, value, regex.IGNORECASE | regex.MULTILINE)
nbr = sep[0]
# On supprime le texte de la variable "nbr" de l'adresse (et on enlève les espaces en trop)
invexpr = regex.compile(nbr)
name = regex.sub(invexpr, '', value, regex.IGNORECASE | regex.MULTILINE)
name = name.strip()
# On retourne une liste avec le groupe numérique d'un côté, le nom de rue de l'autre
return (nbr,name)
# Une fonction qui sépare les numéros (hors rangées)
def splitNumber(self, value):
# Le regex qui évalue là où il faut séparer les numéros
expr = r"((?<=\d)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\da)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\db)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dt)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dbis)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dter)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))"
# Remplacement de chaque séparateur par un pipe ("|")
repl = regex.sub(expr, "|", value, regex.MULTILINE | regex.IGNORECASE)
# Séparation en liste
splitted = regex.split('\|', repl, regex.MULTILINE | regex.IGNORECASE)
# Suppression des espaces en début et fin de chaîne
stripped = [x.strip() for x in splitted]
# Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
value = list(filter(None, stripped))
return value
#Une fonction qui interpole les numéros manquants à partir des rangées
def splitRanges(self,value):
# Le regex qui évalue là où il faut séparer les numéros
expr = r"((?<=\d)\s?(au|à)\s?(?=\d))|((?<=\da)\s?(au|à)\s?(?=\d))|((?<=\db)\s?(au|à)\s?(?=\d))|((?<=\dt)\s?(au|à)\s?(?=\d))|((?<=\dbis)\s?(au|à)\s?(?=\d))|((?<=\dter)\s?(au|à)\s?(?=\d))"
# Remplacement de chaque séparateur par un pipe ("|")
repl = regex.sub(expr, "|", value, regex.MULTILINE | regex.IGNORECASE)
# Séparation en liste
splitted = regex.split('\|', repl, regex.MULTILINE | regex.IGNORECASE)
# Suppression des espaces en début et fin de chaîne
stripped = [x.strip() for x in splitted]
# Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
listed = list(filter(None, stripped))
# Si c'est effectivement une rangée :
if len(listed) > 1 :
# On initie une liste vide et un index correspondant au premier numéro de la rangée
rawList = []
index = int(listed[0])
# Tant que l'index n'est pas supérieur au dernier numéro de la rangée...
while index <= int(listed[1]):
# Inscrire l'index dans la liste
rawList.append(index)
# augmenter l'index d'un numéro
index += 1
# Un petit check pour supprimer les numéros pairs ou impairs selon la parité du premier numéro.
if int(listed[0])%2 == 0 :
for i in rawList :
if i%2 != 0 :
rawList.remove(i)
if int(listed[0])%2 != 0 :
for i in rawList :
if i%2 == 0 :
rawList.remove(i)
return rawList
else :
return value
def removeAbrog(self,value):
expr = r"(?:((main\s?-?levée)|(abrog))){i<=2,d<=2,e<=3}"
part = r"(?:(partiel)){i<=2,d<=2,e<=3}"
if bool(regex.findall(expr, value, regex.MULTILINE | regex.IGNORECASE)):
if bool(regex.findall(part, value, regex.MULTILINE | regex.IGNORECASE)):
return 1 # partiellement abrogé
else :
return 2 # abrogé
else :
return 0
def typeOf(self,value):
# mise en sécurité
peril = r"(?:((?<!périmètre)(sécurité|péril))){i<=2,d<=2,e<=2}"
# périmètre de sécurité
perimetre = r"(?:(périmètre)){i<=2,d<=2,e<=3}"
# imminent
urgent = r"(?:(urgent|imminent|grave)){i<=2,d<=2,e<=3}"
# interdiction d'occupation
interdi = r"(?:((?<=(occup)).*(interdi)|(?<=(util)).*(interdi)|(interdi).*(?=(occup))|(interdi).*(?=(util)))){i<=2,d<=2,e<=3}"
# déconstruction
deconstr = r"(?:(déconstr)){i<=2,d<=2,e<=3}"
# astreinte administrative
astr = r"(?:(astreinte)){i<=2,d<=2,e<=3}"
if bool(regex.findall(perimetre, value, regex.MULTILINE | regex.IGNORECASE)):
return "Périmètre de sécurité"
elif bool(regex.findall(interdi, value, regex.MULTILINE | regex.IGNORECASE)):
return "Interdiction d'occupation"
elif bool(regex.findall(deconstr, value, regex.MULTILINE | regex.IGNORECASE)):
return "Déconstruction"
elif bool(regex.findall(astr, value, regex.MULTILINE | regex.IGNORECASE)):
return "Astreinte administrative"
elif bool(regex.findall(peril, value, regex.MULTILINE | regex.IGNORECASE)):
if bool(regex.findall(urgent, value, regex.MULTILINE | regex.IGNORECASE)):
return "Péril imminent"
else :
return "Péril"
else :
return ""
# La fonction principale de traitement du résultat de la requête de scrapy avant yield
def process_spider_output(self, response, result, spider):
# Pour chaque résultat individuel dans le résultat de la requête :
for r in result:
# Extraire le texte brut de l'adresse et le stocker dans la variable "adresses"
adresses = r.pop('adrs')
# Extraire le dernier arrêté et le stocker dans la variable "dernA"
dernA = r.pop('dernierA')
As = r.pop('As')
raw = r.pop('raw')
# Ne prendre que le premier index de la liste
# il n'y en a qu'un de toute manière, c'est pour extraire le texte de l'objet car regex.py ne sait pas
# traiter les objets tuple et scrapy retourne un objet tuple, pas seulement une chaîne de caractères
adresses = adresses[0]
dernA = dernA[0]
# Si l'adresse est non-nulle :
# (pour éviter que regex.py ne plante à cause d'un objet de type None au lieu d'une chaîne de caractères)
# (ça supprime par la même occasion les arrêtés sans adresse, à date il n'y en a qu'un, sur un passage privé)
if dernA :
abrog = self.removeAbrog(dernA)
if abrog == 1:
typeof = "(Mainlevée partielle) "+ self.typeOf(dernA)
elif abrog == 2:
typeof = "Mainlevée"
else :
typeof = self.typeOf(dernA)
if adresses :
# On apelle la fonction splitStreet pour séparer les rues dans la variable "adresses" et les inscrire dans la liste "indiv"
indiv = self.splitStreet(adresses)
# Pour chaque adresse de la liste d'adresses "indiv" précemment obtenue :
for i in indiv:
# Si l'adresse est utile
if self.isWorth(i) :
iClean = self.removeClutter(i)
iSep = self.separateNbr(iClean)
iNbrs = self.splitNumber(iSep[0])
for r in iNbrs:
x = self.splitRanges(r)
print(x)
if isinstance(x, list):
for n in x:
yield {'':n,'Nom de rue':iSep[1],'Statut':typeof,'Dernier arrêté (hors modificatif)':dernA,'Arrêtés':As,'Données brutes':raw,"QGIS-RAW":str(n)+" "+iSep[1],"QGIS-City":"Marseille","QGIS-Country":"France"}
else :
yield {'':r,'Nom de rue':iSep[1],'Statut':typeof,'Dernier arrêté (hors modificatif)':dernA,'Arrêtés':As,'Données brutes':raw,"QGIS-RAW":str(r)+" "+iSep[1],"QGIS-City":"Marseille","QGIS-Country":"France"}