bonsoir,

j'ai un toc, ce sont les batiments chevauchants. Voila c'est dit!
ci-joint un blabla sur le sujet (il vaut que ce qu'il vaut)
et un script python a tester sur les fichiers cadastre.openstreetmap.fr

Les dépendances sont 
 + Osmsax d'osmose :
https://gitorious.org/osmose/backend/blobs/master/analyser_sax.py

shapely (python-shapely)
rtree : http://pypi.python.org/pypi/Rtree 
rtree necessite libspatialindex http://libspatialindex.github.com/

Merci de vos remarques

didier

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#       building4_qadastre.py
#       
#       Copyright 2012 didier <[email protected]>
#       
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.
#
#		Programme censé corriger des erreurs de chevauchements dans 
#		les fichiers généré par qadastre2osm 
#		(qadastre2osm generate OpenStreetMap files from the cadastre through its PDF export.)
#       http://gitorious.org/qadastre/qadastre2osm
#
#
#################################################################################################
import OsmSax, sys, copy, time
from rtree import index
# speedus est integer depuis la version 1.2.10. de shapely
#from shapely import speedups
from shapely.geometry import Point, Polygon

#surface maximum de l'intersection pour une correction
SEUIL = 1.0e-7

def coords(n):
	return (n.lat, n.lon)

class Node(object):
    def __init__(self, id=None, lon=None, lat=None, tags=None):
        self.id = id
        if lon != None: self.lon, self.lat = float(lon), float(lat)
        if tags:
            self.tags = tags
        else:
            self.tags = {}
        self.inWay = set()
        self.inRel = set()

class Way(object):
    def __init__(self, id=None, nodes=None, tags=None):
        self.id = id
        if nodes:
            self.nodes = nodes
        else:
            self.nodes = []
        if tags:
            self.tags = tags
        else:
            self.tags = {}

class Relation(object):
    def __init__(self, id, members=None, tags=None):
        self.id = id
        if members:
          self.members = members
        else:
          self.members = []
        if tags:
          self.tags = tags
        else:
          self.tags = {}
    def __repr__(self):
      return "Relation(id=%r, members=%r, tags=%r)" % (self.id, self.members, self.tags)

class CacheDataOsm:
    def __init__(self):
        self.nods = {}
        self.ways = {}
        self.rels = {}
    def NodeCreate(self, data):
        self.nods[data["id"]] = Node(id=data["id"],lon=data["lon"],lat=data["lat"],tags=data["tag"])
    def WayCreate(self, data):
        self.ways[data["id"]] = Way(id=data["id"],nodes=data["nd"],tags=data["tag"])
    def RelationCreate(self, data):
        self.rels[data["id"]] = Relation(id=data["id"],tags=data["tag"],members=data["member"])

def triliste(xliste):
	# xliste est une liste (liste des nodes d'un way)
	# cette fonction renvoie une liste réordonnée
	# du plus petit au plus grand
	# qui permet
	# d'ordonner de façon unique toutes les combinaisons de description d'un way fermé
	
	# la plus petite valeur de la liste
	miref=min(xliste)
	# la plus grande valeur de la liste
	maref=max(xliste)
	
	# premier tri de la liste
	# position de l'element miref dans la liste
	pmiref=xliste.index(miref)
	# réordonnage de la premiere liste
	newl1=xliste[pmiref:len(xliste)]+xliste[1:pmiref+1]
	# ecart de position des élément minref et maxref dans la premiere liste
	opti1=newl1.index(maref)-newl1.index(miref)
	
	# creation liste temporaire (pour inversion de l'ordre)
	tmpl=xliste[pmiref:len(xliste)]+xliste[1:pmiref+1]
	# inversion de l'ordre des éléments de la liste
	tmpl.reverse()
	# position de l'element miref dans la liste temporaire
	tpmiref=tmpl.index(miref)
	
	# deuxieme tri de la liste (tri de la liste temporaire)
	# deuxieme liste réordonnée
	newl2=tmpl[tpmiref:len(tmpl)]+tmpl[1:tpmiref+1]
	# critere de description (a^2 + b^3 + ...+ a^n)
	opti2=0
	cpt=1
	for k2 in newl2 :
		cpt += 1
		opti2 += k2^cpt

	# il est peut probable que opti1=opti2  
	# donc quelque soit la facon dont le way est decrit
	# cette fonction retournera toujours la meme liste

	if opti1>opti2 :
		return newl2
	else :
		return newl1	
	
def listetostring(xliste) :
	stringx=''
	for k in xliste :
		stringx=stringx+str(k)+";"
	return stringx
	


###########################################################################

avant = time.clock()
# fichier a analyser / corriger en premier argument de la ligne de commande
data = OsmSax.OsmSaxReader(sys.argv[1])
#fout = sys.argv[2]
	
#data = OsmSax.OsmSaxReader("Y0691-YERRES-houses.osm")
#data = OsmSax.OsmSaxReader("D0229-EVREUX-houses.osm")
fout = 'chevauche-corrige.osm'
	
dataosm = CacheDataOsm()

print 'Parse du fichier...'
data.CopyTo(dataosm)
print "nombre de node", len(dataosm.nods)
print "nombre de way ", len(dataosm.ways)


# Initialisation des variables
lstCoordId = {}
lstIdCoord = {}
lstPtFinal = {}
lstBounds = {}

listeway={}
c1=[]
c2=[]
nxc1=[]
nxc2=[]
	
print 'Indexation node...'
# pour retrouver l'id d'un tuple de coordonnée
# pour retrouver le tuple de coordonnée d'un id
# ceci pour les nodes existants ou créés
minid=-1
	
for xy in dataosm.nods.keys():
	# pseudo index
	lstCoordId[coords(dataosm.nods[xy])]=dataosm.nods[xy].id
	lstIdCoord[dataosm.nods[xy].id]=coords(dataosm.nods[xy])
	# pour connaitre l'id minimum des node de ce fichier
	if (dataosm.nods[xy].id < minid) :
		minid=dataosm.nods[xy].id

# raw_input("Appuyer sur une touche ")	
#initialisation du compteur de nouveau node
if minid <0:
	nxnx=-minid+1
else :	
	nxnx=1
print "premier numero des nodes créés:", -nxnx

print 'Indexation way....'
idx = index.Index()
# balayage de tous les ways
cptidx=0
for wx in dataosm.ways.values() :
	cptidx+=1

	minlat=99999
	minlon=99999
	maxlat=-99999
	maxlon=-99999
	for pt in wx.nodes :
		if dataosm.nods[pt].lat< minlat : minlat=dataosm.nods[pt].lat
		if dataosm.nods[pt].lon< minlon : minlon=dataosm.nods[pt].lon
		if dataosm.nods[pt].lat> maxlat : maxlat=dataosm.nods[pt].lat
		if dataosm.nods[pt].lon> maxlon : maxlon=dataosm.nods[pt].lon
	lstBounds[wx.id]=(minlon,minlat,maxlon,maxlat)
	idx.insert(cptidx,lstBounds[wx.id],wx.id)
	
	
	
print 'Correction des chevauchements... patience !'	

methode="PointDedans"

for w1 in dataosm.ways.values() :
	# coordonnées et id des noeuds
	del(c1[:])
	#récupération des coordonnées des points du way
	for a in w1.nodes:
		#c1.append(coords(dataosm.nods[xy]))
		c1.append(lstIdCoord[a])
	
	# construction surface 
	s1= Polygon(c1)
	# vérification sur les buildings, pas les inner sans attributs => a corriger manuellement
	if not ("building" in w1.tags) : continue
	# verification si le way est fermé (le premier et dernier ref du way sont le même node)
	if not (w1.nodes[0] == w1.nodes[len(w1.nodes)-1]) : continue
	#verification que la forme est géometriquement valide
	if (len(w1.nodes)<4) : continue
	if s1.is_valid and s1.geom_type=="Polygon":
		# liste de tous les way contenus dans le bbox de w1
		listew2 = list(idx.intersection(lstBounds[w1.id], objects="raw"))
		for widx in listew2:
			w2=dataosm.ways[widx]
			# pour ne pas traiter son propre chevauchement
			if (w1.id <> w2.id) :
				if len(w2.nodes)<4 : continue
				# verification si le way est fermé (le premier et dernier ref du way sont le même node)
				if not (w2.nodes[0] == w2.nodes[len(w2.nodes)-1]) : continue
				if not ("building" in w2.tags) : continue
				# coordonnées et id des noeuds
				del(c2[:])
				#récupération des coordonnées des points du way
				for b in w2.nodes :
					#c2.append(coords(dataosm.nods[xy]))
					c2.append(lstIdCoord[b])
				# construction surface 
				s2= Polygon(c2)
				#verification que la forme est géometriquement valide
				if not s2.is_valid : continue
				if s2.geom_type=="Polygon" :
					# les 2 formes ont une intersection
					if (s1.intersects(s2)==True) :
						# la surface d'intersection
						ix=s1.intersection(s2)					
						# pour ne pas corriger des chevauchements trop grand
						if not (ix.geom_type=="Polygon") : continue
						if (ix.area > 0 ) and (ix.area < SEUIL) :
							#print w1.id, w2.id, i2.area*10000000

							# nombre de node de s1 contenu dans s2 < nombre de node de s2 contenu dans s1
							# s1.contains(les Point de s2)>s2.contains(les Point de s1)
							if methode=="PointDedans":
# ----------- Methode 1 pour savoir qui modifier 
								npt1=0
								for xy in c2 :
									if s1.contains(Point(xy)) :
										npt1+=1
								npt2=0
								for xy in c1 :
									if s2.contains(Point(xy)) :
										npt2+=1
	
								# le plus grand batiment aura des nodes en plus
								# le plus petit batiment aura sa surface réduite, et des nodes seront a supprimer dans josm (validator)
								if npt1>npt2:
									castraiter=1
								else:
									castraiter=2
							else :
# ----------- Methode 2 pour savoir qui modifier 								
								if s1.area>s2.area:
									castraiter=1
								else :
									castraiter=2
								
							if castraiter==1 :
								# le cas 1
								# calcul des 3 surfaces dues aux 2 batiments 
								i1=s1.difference(s2)
								i2=s1.intersection(s2)
								i3=s2.difference(s1)
								if i1.is_valid and i2.is_valid and i3.is_valid:
								# nouvelles valeurs pour s1 et s2
									nx1=i1.union(i2)
									nx2=s2.difference(s1)
								else : continue
							else :
								# le cas 2
								i1=s2.difference(s1)
								i2=s2.intersection(s1)
								i3=s1.difference(s2)
								if i1.is_valid and i2.is_valid and i3.is_valid:
									# nouvelles valeurs pour s1 et s2
									nx1=s1.difference(s2)
									nx2=i1.union(i2)
								else : continue
								
							# si les nouvelles surfaces sont ok : valides et de type Polygon (pas une ligne et encore moins un multipolygon)
							if not nx1.is_valid : continue
							if nx2.is_valid :
								#jefai1=0
								#jefai2=0
								#try :
								#   if nx1.geom_type=="Polygon" :
								#	   jefai1=1
								#except:
								#	#print w1.id, w2.id, ix.area
								#	jefai1=0
								#	"un batiment a l'interieur d'un autre . cela cré une exception car nx1 est "empty"=> solution verifier si nx1 est empty
								#	w1.tags['erreur'] = 'exception'
								#	w2.tags['erreur'] = 'exception'
								#
								#try:
								#   if nx2.geom_type=="Polygon" :
								#	   jefai2=1
								#except:
								#	jefai1=0
								#	#print w1.id, w2.id, ix.area
								#	w1.tags['erreur'] = 'exception'
								#	w2.tags['erreur'] = 'exception'
								#
								#if jefai1==1 and jefai2==1:

								if nx1.is_empty : continue
								if nx2.is_empty : continue
								if not (nx1.geom_type=="Polygon"): continue
								if nx2.geom_type=="Polygon" :

									
								# retrouver l'id des noeuds des surfaces générées
	#---------------------- ---  surface 1 -
								# liste des coordonnées de la surface1
									del(nxc1[:])
									for c in nx1.exterior.coords :
										try :
											# exception si xy n'existe pas dans lstCoordId => Nouveau node
											lstPtFinal[c]=lstCoordId[c]
										except :
											nxnx+=1	
											lstPtFinal[c]=-nxnx
											# ajout des nouveaux noeuds 
											# mise a jour des dictionnaires coordonnees:point et point:coordonnees
											lstCoordId[c]=-nxnx
											lstIdCoord[-nxnx]=c
								
									# liste des nodes du nouveau way 
									for e in nx1.exterior.coords :
										nxc1.append(lstCoordId[e])
		
#---------------------------  	surface 2
									del(nxc2[:])
									for f in nx2.exterior.coords :
										try :
											# exception si xy n'existe pas dans lstCoordId => Nouveau node
											lstPtFinal[f]=lstCoordId[f]
										except :
											nxnx+=1	
											lstPtFinal[f]=-nxnx
											# ajout des nouveaux noeuds 
											# mise a jour des dictionnaires coordonnees:point et point:coordonnees
											lstCoordId[f]=-nxnx
											lstIdCoord[-nxnx]=f
								
									# liste des nodes du nouveau way 
									for h in nx2.exterior.coords :
										nxc2.append(lstCoordId[h])
								
# ------------------------------ 	mise a jour des donnees des ways									
									# modifier les nd des ways 
									s1= Polygon(nx1.exterior.coords)
									
									w1.nodes=copy.deepcopy(nxc1)
									w2.nodes=copy.deepcopy(nxc2)
									# ajout tag pour vérification: a supprimer dans josm ou dans le script...
									w1.tags['barrier'] = 'wall'
									w2.tags['barrier'] = 'wall'
									
# Corrections du fichier des ways pour optimiser le validator JOSM
doublon={}
nberr1=0
nberr2=0
for w in dataosm.ways.values():
	# verification si le way est fermé (le premier et dernier ref du way sont le même node)
	if w.nodes[0] == w.nodes[len(w.nodes)-1] :
		if len(w.nodes)<4 :
			# ajout d'un tag sans attribu pour générer une erreur par le validator de josm
			w.tags['fixme']=''
			w.tags['note']='way ferme avec moins de 2 noeuds , ce n''est pas une surface'
			nberr1 += 1
		else :
			# nouvelle liste réordonnée
			newl=triliste(w.nodes)
			# affectation a la liste existante des nouvelles valeurs
			w.nodes=copy.deepcopy(newl)
			#liste des nodes separés par ;
			tmps=listetostring(w.nodes)
			# verification si la liste des nodes du way est déja dans la liste => doublon
			if tmps in doublon :
				# ajout du tag pour identifier le doublon dans josm
				# attention pour les doubles inner dans les relations
				# par exemple un buiding=yes contenu dans un building= yes et wall=no 
				nberr2 += 1
				#w.tags['fixme']='doublon'
			else :
				# sinon on ajoute l'element
				doublon[tmps]=1
			
print "Il y a %s erreur(s) et %s doublon(s)." % (nberr1,nberr2)

print 'Ecriture...'
out = OsmSax.OsmSaxWriter(fout, "UTF-8")
out.startDocument()
out.startElement("osm", {'version':'0.6','generator':'ScriptDidier'})

# ajout des nouveaux node 
cpt=0
for clef, valeur in lstCoordId.items() :
	if valeur <= minid :
		# il reste une faille: les noeuds non utilisé dans les ways...
		# il suffit de lancer le validator de josm et de virer les erreurs
		out.NodeCreate({'id':valeur,'lat':clef[0],'lon':clef[1],'tag':{'fixme':'ajout par script'}})
		cpt+=1
print "nombre de node ajoutés: ", cpt

# réécriture des données existantes - existantes modifiées
for n in dataosm.nods.values():
	if n.id> minid :
		out.NodeCreate({'id':n.id,'lon':n.lon,'lat':n.lat,'tag':n.tags})
	else : cpt+=1
print "nombre de node supprimés: ", cpt
		
for w in dataosm.ways.values():
	out.WayCreate({'id':w.id,'nd':w.nodes,'tag':w.tags})
for r in dataosm.rels.values():
	out.RelationCreate({'id':r.id,'member':r.members,'tag':r.tags})

out.endElement("osm")
print 'Temps d''execution : ',time.clock() - avant


Préambule:
        je n'incrimine personne ou auqu'un outil. 
        J'énumère des points constatés récurrents par analyse quantitative, 
correction ou visualisation

        Les requetes effectuées sont un extract france de geofabrik. 
        Le schéma utilisé est celui d'osm2pgsl- ce qui fausser les chiffres
        (relation / batiment , layer distinct, ...)

Données utilisées mars 2012:
        france.osm = 43.6 go, dont 31.6 go pour les batiments ( 11 pour les way 
et 20 pour les node).
        soit 25 224 467 batiments dont 36 mille avec des relations

Bâtiments chevauchants:
        Pourquoi
        - Ils existent dans les données issues des scripts qui permettent 
l'import semi-automatique des bâtiments
                + la précision du cadastre ?, la densité de batiments 
                + le type de ligne délimitant les parcelles
                + toutes les erreurs détéctées par osmose après upload ne le 
sont pas par le validator de josm avant l'upload
                + plusieurs relations pour le meme outer mais chacune ayant un 
inner différent (probleme évoqué sur la liste talk-fr, a prioris corrigé?)
                + ways en doubles (peu nombreux mais dont la correction est 
facile)
        - Apres l'upload des données, des bâtiments se chevauchent
                + il y a des superposition avec des batiment qui existaient 
auparavent
                        ++ dans la meme commune
                        ++ au limites des communes 
                + fausse manipulation/problème lors de l'upload qui font que 
les batiments sont en plusieurs exemplaire ?
                + ...

Analyse quantitative
        Pour la france, le taux d'erreur est de 0.9% (nombre total de 
batiments/ nombre d'erreur détecté par osmose)
        18 millions de batiments et plus de 200 mille chevauchements. (7 
millions sans chevauchement)

Surface de l'intersection
        82% fait moins de 2 m2
        4% est compris entre 2 et 10 m2
        le reste varie jusqu'a 2884m2.
        Les plus grosses valeurs concernent zone commerciale, hopital, batiment 
industriel 
        (les batiments avec de grande surface peuvent être a l'origine de 
plusieurs erreurs de chevauchement détectées par osmose)

Nombre de Points d'intersection
        1 point  : 103009 => overlaps 1 node ?
        2 points : 37767  
        3 points : 34816
        4 points : 5434
        5 points : 14595
        6 points : 2750
        7 points : 14595
        jusqu'a 184 points d'interection !

Solutions:
        + Mettre en avant l'api openstreetmap.fr de sly: auparavant, il était 
fastidieux de retélécharger un upload du quadastre , ce qui a pu décourager 
certains de la correction post upload
        + voir avec l'equipe d'osmose s'il est possible de distinguer la 
"gravité" du chevauchement (critére de surface ou rapport entre la surface de 
l'intersection et la plus petite surface des batiments concernés
        + des scripts de correction qui se situerait entre Qadastre2osm et Josm.
        + ...
_______________________________________________
dev-fr mailing list
[email protected]
http://lists.openstreetmap.org/listinfo/dev-fr

Répondre à