ESCatastroLib 0.0.1rc5__py2.py3-none-any.whl → 1.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ESCatastroLib/__about__.py +1 -1
- ESCatastroLib/models/InfoCatastral.py +306 -207
- {escatastrolib-0.0.1rc5.dist-info → escatastrolib-1.0.0.dist-info}/METADATA +35 -5
- {escatastrolib-0.0.1rc5.dist-info → escatastrolib-1.0.0.dist-info}/RECORD +6 -8
- {escatastrolib-0.0.1rc5.dist-info → escatastrolib-1.0.0.dist-info}/WHEEL +1 -1
- ESCatastroLib/utils/test_plantas.py +0 -109
- ESCatastroLib/utils/test_valor_catastral_tierras.py +0 -136
- {escatastrolib-0.0.1rc5.dist-info → escatastrolib-1.0.0.dist-info}/licenses/LICENSE +0 -0
ESCatastroLib/__about__.py
CHANGED
|
@@ -3,8 +3,8 @@ import json
|
|
|
3
3
|
import xmltodict
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
|
-
from shapely import Point
|
|
7
|
-
from typing import Union
|
|
6
|
+
from shapely.geometry import Point
|
|
7
|
+
from typing import Union, Dict, Any, Optional, List
|
|
8
8
|
from pyproj import Transformer
|
|
9
9
|
|
|
10
10
|
from ..utils.statics import URL_BASE_CALLEJERO, URL_BASE_GEOGRAFIA, URL_BASE_CROQUIS_DATOS, URL_BASE_MAPA_VALORES_URBANOS, URL_BASE_MAPA_VALORES_RUSTICOS, URL_BASE_WFS_EDIFICIOS, CULTIVOS
|
|
@@ -13,7 +13,98 @@ from ..utils.exceptions import ErrorServidorCatastro
|
|
|
13
13
|
from ..utils import converters
|
|
14
14
|
from .Calle import Calle, Municipio
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
class ParcelaHelper:
|
|
18
|
+
"""Clase base con métodos compartidos para operaciones de parcelas catastrales."""
|
|
19
|
+
|
|
20
|
+
def _llamar_a_api(self, url: str, params: Dict[str, Any], timeout: int = 30) -> requests.Response:
|
|
21
|
+
"""Realiza una petición HTTP a la API del Catastro.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
url: URL del endpoint a consultar
|
|
25
|
+
params: Parámetros de la petición HTTP
|
|
26
|
+
timeout: Tiempo máximo de espera en segundos
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Objeto Response con la respuesta del servidor
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ErrorServidorCatastro: Si el servidor devuelve una respuesta vacía o error
|
|
33
|
+
"""
|
|
34
|
+
response = requests.get(url, params=params, timeout=timeout)
|
|
35
|
+
|
|
36
|
+
if len(response.content) == 0:
|
|
37
|
+
raise ErrorServidorCatastro("El servidor ha devuelto una respuesta vacía")
|
|
38
|
+
|
|
39
|
+
return response
|
|
40
|
+
|
|
41
|
+
def _parsear_respuesta(self, response: requests.Response) -> Dict[str, Any]:
|
|
42
|
+
"""Parsea la respuesta JSON de la API del Catastro.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
response: Objeto Response de la petición HTTP
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Diccionario con los datos parseados
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ErrorServidorCatastro: Si el servidor no devuelve un JSON válido
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
return response.json()
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
raise ErrorServidorCatastro(
|
|
57
|
+
mensaje=f"El servidor no devuelve un JSON. Mensaje en bruto: {response.content}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _parametrizar_peticion(self, **kwargs) -> Dict[str, Any]:
|
|
61
|
+
"""Construye un diccionario de parámetros para la petición HTTP.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
**kwargs: Parámetros clave-valor para la petición
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Diccionario con los parámetros filtrados (solo valores no nulos)
|
|
68
|
+
"""
|
|
69
|
+
return {k: v for k, v in kwargs.items() if v is not None}
|
|
70
|
+
|
|
71
|
+
def _comprobar_errores_catastro(self, info_cadastre: Dict[str, Any]) -> bool:
|
|
72
|
+
"""Verifica si la respuesta del Catastro contiene errores.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
info_cadastre: Diccionario con la respuesta del Catastro
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True si no hay errores, False si hay errores
|
|
79
|
+
"""
|
|
80
|
+
return comprobar_errores(info_cadastre)
|
|
81
|
+
|
|
82
|
+
def _obtener_numero_parcelas(self, info_cadastre: Dict[str, Any], consulta_key: str) -> int:
|
|
83
|
+
"""Obtiene el número de parcelas de una respuesta del Catastro.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
info_cadastre: Diccionario con la respuesta del Catastro
|
|
87
|
+
consulta_key: Clave de la consulta ('dnprc', 'dnppp', 'dnploc')
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Número de parcelas encontradas
|
|
91
|
+
"""
|
|
92
|
+
result_key = f"consulta_{consulta_key}Result"
|
|
93
|
+
return info_cadastre.get(result_key, {}).get("control", {}).get("cudnp", 1)
|
|
94
|
+
|
|
95
|
+
def _extraer_rc_from_dict(self, rc_dict: Dict[str, Any]) -> str:
|
|
96
|
+
"""Extrae la referencia catastral de un diccionario.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
rc_dict: Diccionario que contiene la referencia catastral
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Referencia catastral como string
|
|
103
|
+
"""
|
|
104
|
+
return ''.join(rc_dict.values())
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ParcelaCatastral(ParcelaHelper):
|
|
17
108
|
"""
|
|
18
109
|
Clase que representa una parcela catastral.
|
|
19
110
|
Args:
|
|
@@ -48,7 +139,15 @@ class ParcelaCatastral:
|
|
|
48
139
|
geometria (list): Una lista de puntos que representan la geometría de la parcela.
|
|
49
140
|
"""
|
|
50
141
|
|
|
51
|
-
def __create_regions(self, info_cadastre:
|
|
142
|
+
def __create_regions(self, info_cadastre: Dict[str, Any]) -> None:
|
|
143
|
+
"""Crea la lista de regiones de la parcela a partir de los datos del Catastro.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
info_cadastre: Diccionario con la respuesta del Catastro.
|
|
147
|
+
|
|
148
|
+
Attributes:
|
|
149
|
+
regiones: Lista de diccionarios con 'descripcion' y 'superficie' de cada región.
|
|
150
|
+
"""
|
|
52
151
|
self.regiones = []
|
|
53
152
|
if self.tipo == 'Urbano':
|
|
54
153
|
iterator = list(info_cadastre.values())[0].get('bico').get('lcons')
|
|
@@ -67,18 +166,26 @@ class ParcelaCatastral:
|
|
|
67
166
|
})
|
|
68
167
|
|
|
69
168
|
|
|
70
|
-
def __create_geometry(self, projection: str = 'EPSG:4326'):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
169
|
+
def __create_geometry(self, projection: str = 'EPSG:4326') -> None:
|
|
170
|
+
"""Crea la geometría de la parcela consultando el servicio WFS.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
projection: Sistema de referencia espacial (EPSG). Por defecto 'EPSG:4326'.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
ErrorServidorCatastro: Si el servidor devuelve una respuesta vacía o error.
|
|
177
|
+
"""
|
|
178
|
+
params = self._parametrizar_peticion(
|
|
179
|
+
service='wfs',
|
|
180
|
+
version='2',
|
|
181
|
+
request='getfeature',
|
|
182
|
+
STOREDQUERIE_ID='GetParcel',
|
|
183
|
+
refcat=self.rc,
|
|
184
|
+
srsname=projection
|
|
185
|
+
)
|
|
186
|
+
response = self._llamar_a_api(f'{URL_BASE_GEOGRAFIA}', params)
|
|
187
|
+
|
|
188
|
+
geometry = xmltodict.parse(response.content)
|
|
82
189
|
geoposition = geometry.get('FeatureCollection').get('member').get('cp:CadastralParcel').get('cp:referencePoint').get('gml:Point').get('gml:pos').split(' ')
|
|
83
190
|
self.centroide = {
|
|
84
191
|
'x': geoposition[1],
|
|
@@ -95,67 +202,56 @@ class ParcelaCatastral:
|
|
|
95
202
|
|
|
96
203
|
def __create_from_rc(self, rc: str, projection: str):
|
|
97
204
|
"""Create an instance of InfoCatastral from a RC (Referencia Catastral) string."""
|
|
98
|
-
|
|
99
|
-
|
|
205
|
+
params = self._parametrizar_peticion(RefCat=rc)
|
|
206
|
+
response = self._llamar_a_api(f'{URL_BASE_CALLEJERO}/Consulta_DNPRC', params)
|
|
207
|
+
info_cadastre = self._parsear_respuesta(response)
|
|
100
208
|
|
|
101
|
-
if
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
raise ErrorServidorCatastro(mensaje=
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
else
|
|
112
|
-
|
|
113
|
-
self.
|
|
114
|
-
self.
|
|
115
|
-
self.
|
|
116
|
-
self.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
self.poligono = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lors').get('lorus').get('cpp').get('cpo')
|
|
125
|
-
self.nombre_paraje = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lors').get('lorus').get('npa')
|
|
126
|
-
|
|
127
|
-
self.__create_regions(info_cadastre)
|
|
128
|
-
self.__create_geometry(projection)
|
|
129
|
-
|
|
130
|
-
self.superficie_construida = sum(float(region.get('superficie')) for region in self.regiones)
|
|
131
|
-
self.superficie = sum(float(region.get('superficie')) for region in self.regiones)
|
|
132
|
-
else:
|
|
133
|
-
raise ErrorServidorCatastro("El servidor ha devuelto una respuesta vacia")
|
|
209
|
+
if self._comprobar_errores_catastro(info_cadastre):
|
|
210
|
+
cudnp = self._obtener_numero_parcelas(info_cadastre, 'dnprc')
|
|
211
|
+
|
|
212
|
+
if cudnp > 1:
|
|
213
|
+
raise ErrorServidorCatastro(mensaje="Esta parcela tiene varias referencias catastrales. Usa un objeto MetaParcela.")
|
|
214
|
+
else:
|
|
215
|
+
self.rc = self._extraer_rc_from_dict(info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('idbi').get('rc'))
|
|
216
|
+
self.url_croquis = requests.get(URL_BASE_CROQUIS_DATOS, params={'refcat': self.rc}).url
|
|
217
|
+
self.municipio = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('nm')
|
|
218
|
+
self.provincia = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('np')
|
|
219
|
+
self.tipo = 'Rústico' if info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('idbi').get('cn') == 'RU' else 'Urbano'
|
|
220
|
+
if self.tipo == 'Urbano':
|
|
221
|
+
self.calle = f"{info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lous').get('lourb').get('dir').get('tv')} {info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lous').get('lourb').get('dir').get('nv')}"
|
|
222
|
+
self.numero = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lous').get('lourb').get('dir').get('pnp')
|
|
223
|
+
self.antiguedad = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('debi').get('ant')
|
|
224
|
+
self.uso = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('debi').get('luso')
|
|
225
|
+
elif self.tipo == 'Rústico':
|
|
226
|
+
self.parcela = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lors').get('lorus').get('cpp').get('cpa')
|
|
227
|
+
self.poligono = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lors').get('lorus').get('cpp').get('cpo')
|
|
228
|
+
self.nombre_paraje = info_cadastre.get('consulta_dnprcResult').get('bico').get('bi').get('dt').get('locs').get('lors').get('lorus').get('npa')
|
|
229
|
+
|
|
230
|
+
self.__create_regions(info_cadastre)
|
|
231
|
+
self.__create_geometry(projection)
|
|
134
232
|
|
|
233
|
+
self.superficie_construida = sum(float(region.get('superficie')) for region in self.regiones)
|
|
234
|
+
self.superficie = sum(float(region.get('superficie')) for region in self.regiones)
|
|
235
|
+
|
|
135
236
|
def __create_from_parcel(self, provincia: Union[str,None], municipio: Union[str,None], poligono: Union[str,None], parcela: Union[str,None], projection: str):
|
|
136
237
|
"""Create an instance of InfoCatastral from a parcela string."""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
raise ErrorServidorCatastro(mensaje=f"El servidor no devuelve un JSON. Mensaje en bruto: {req1.content}")
|
|
149
|
-
if comprobar_errores(info_cadastre):
|
|
150
|
-
cudnp = info_cadastre.get("consulta_dnpppResult", {}).get("control", {}).get("cudnp", 1)
|
|
238
|
+
params = self._parametrizar_peticion(
|
|
239
|
+
Provincia=provincia,
|
|
240
|
+
Municipio=municipio,
|
|
241
|
+
Poligono=poligono,
|
|
242
|
+
Parcela=parcela
|
|
243
|
+
)
|
|
244
|
+
response = self._llamar_a_api(f'{URL_BASE_CALLEJERO}/Consulta_DNPPP', params)
|
|
245
|
+
info_cadastre = self._parsear_respuesta(response)
|
|
246
|
+
|
|
247
|
+
if self._comprobar_errores_catastro(info_cadastre):
|
|
248
|
+
cudnp = self._obtener_numero_parcelas(info_cadastre, 'dnppp')
|
|
151
249
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
else:
|
|
158
|
-
raise ErrorServidorCatastro("El servidor ha devuelto una respuesta vacia")
|
|
250
|
+
if cudnp > 1:
|
|
251
|
+
raise ErrorServidorCatastro(mensaje="Esta parcela tiene varias referencias catastrales. Usa un objeto MetaParcela.")
|
|
252
|
+
else:
|
|
253
|
+
self.rc = self._extraer_rc_from_dict(info_cadastre.get('consulta_dnpppResult').get('bico').get('bi').get('idbi').get('rc'))
|
|
254
|
+
self.__create_from_rc(self.rc, projection)
|
|
159
255
|
|
|
160
256
|
def __create_from_address(self, provincia: Union[str,None], municipio: Union[str,None], tipo_via: Union[str,None], calle: Union[str,None], numero: Union[str,None], projection: str):
|
|
161
257
|
"""Create an instance of InfoCatastral from an address string."""
|
|
@@ -169,37 +265,31 @@ class ParcelaCatastral:
|
|
|
169
265
|
)
|
|
170
266
|
|
|
171
267
|
if info_calle:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
except:
|
|
185
|
-
raise ErrorServidorCatastro(mensaje=f"El servidor no devuelve un JSON. Mensaje en bruto: {req1.content}")
|
|
186
|
-
cudnp = info_cadastre.get("consulta_dnplocResult", {}).get("control", {}).get("cudnp", 1)
|
|
268
|
+
params = self._parametrizar_peticion(
|
|
269
|
+
Provincia=info_calle.municipio.provincia,
|
|
270
|
+
Municipio=info_calle.municipio.municipio,
|
|
271
|
+
Sigla=info_calle.tipo_via,
|
|
272
|
+
Calle=info_calle.calle,
|
|
273
|
+
Numero=numero
|
|
274
|
+
)
|
|
275
|
+
response = self._llamar_a_api(f'{URL_BASE_CALLEJERO}/Consulta_DNPLOC', params)
|
|
276
|
+
info_cadastre = self._parsear_respuesta(response)
|
|
277
|
+
|
|
278
|
+
if self._comprobar_errores_catastro(info_cadastre):
|
|
279
|
+
cudnp = self._obtener_numero_parcelas(info_cadastre, 'dnploc')
|
|
187
280
|
|
|
188
281
|
if cudnp > 1:
|
|
189
282
|
raise ErrorServidorCatastro(mensaje="Esta parcela tiene varias referencias catastrales. Usa un objeto MetaParcela.")
|
|
190
283
|
else:
|
|
191
284
|
if 'lrcdnp' in info_cadastre.get('consulta_dnplocResult'):
|
|
192
|
-
self.rc =
|
|
285
|
+
self.rc = self._extraer_rc_from_dict(info_cadastre.get('consulta_dnplocResult').get('lrcdnp').get('rcdnp')[0].get('rc'))
|
|
193
286
|
elif 'bico' in info_cadastre.get('consulta_dnplocResult'):
|
|
194
|
-
self.rc =
|
|
287
|
+
self.rc = self._extraer_rc_from_dict(info_cadastre.get('consulta_dnplocResult').get('bico').get('bi').get('idbi').get('rc'))
|
|
195
288
|
self.__create_from_rc(self.rc, projection)
|
|
196
|
-
elif 'lerr' in
|
|
197
|
-
info_cadastre = json.loads(req.content)
|
|
289
|
+
elif 'lerr' in info_cadastre.get('consulta_dnplocResult') and info_cadastre['consulta_dnplocResult']['lerr'][0]['cod'] == '43':
|
|
198
290
|
raise Exception(f"Ese número no existe. Prueba con alguno de estos: {[num.get('num').get('pnp') for num in info_cadastre.get('consulta_dnplocResult').get('numerero').get('nump')]}")
|
|
199
291
|
else:
|
|
200
292
|
raise ErrorServidorCatastro("El servidor ha devuelto una respuesta vacia")
|
|
201
|
-
|
|
202
|
-
|
|
203
293
|
else:
|
|
204
294
|
raise Exception('La calle no existe.')
|
|
205
295
|
|
|
@@ -225,9 +315,11 @@ class ParcelaCatastral:
|
|
|
225
315
|
raise ValueError("No se ha proporcionado suficiente información para realizar la búsqueda")
|
|
226
316
|
|
|
227
317
|
@property
|
|
228
|
-
def distancias_aristas(self):
|
|
229
|
-
"""
|
|
230
|
-
|
|
318
|
+
def distancias_aristas(self) -> Optional[List[float]]:
|
|
319
|
+
"""Calcula las distancias entre puntos consecutivos de la geometría de la parcela.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Lista de distancias entre aristas consecutivas en metros, o None si no hay geometría.
|
|
231
323
|
"""
|
|
232
324
|
if self.geometria:
|
|
233
325
|
distancias = []
|
|
@@ -243,9 +335,11 @@ class ParcelaCatastral:
|
|
|
243
335
|
return None
|
|
244
336
|
|
|
245
337
|
@property
|
|
246
|
-
def perimetro(self):
|
|
247
|
-
"""
|
|
248
|
-
|
|
338
|
+
def perimetro(self) -> Optional[float]:
|
|
339
|
+
"""Calcula el perímetro total de la geometría de la parcela.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Perímetro total en metros, o None si no hay geometría.
|
|
249
343
|
"""
|
|
250
344
|
distancias = self.distancias_aristas
|
|
251
345
|
if distancias:
|
|
@@ -253,19 +347,27 @@ class ParcelaCatastral:
|
|
|
253
347
|
else:
|
|
254
348
|
return None
|
|
255
349
|
|
|
256
|
-
def valor_catastral_urbano_m2(self, anio):
|
|
350
|
+
def valor_catastral_urbano_m2(self, anio: int) -> Optional[float]:
|
|
351
|
+
"""Obtiene el valor catastral por metro cuadrado para parcelas urbanas.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
anio: Año del valor catastral a consultar.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Valor catastral en €/m², 0 si es rústica, o None si hay algún error.
|
|
358
|
+
"""
|
|
257
359
|
if self.tipo == 'Rústico':
|
|
258
360
|
return 0
|
|
259
361
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
362
|
+
params = self._parametrizar_peticion(
|
|
363
|
+
huso="4326",
|
|
364
|
+
x=self.centroide['x'],
|
|
365
|
+
y=self.centroide['y'],
|
|
366
|
+
anyoZV=anio,
|
|
367
|
+
suelo="N",
|
|
368
|
+
tipo_mapa="vivienda"
|
|
369
|
+
)
|
|
370
|
+
req = self._llamar_a_api(f'{URL_BASE_MAPA_VALORES_URBANOS}', params)
|
|
269
371
|
|
|
270
372
|
values_map = converters.gpd.read_file(req.content)
|
|
271
373
|
centroide_point = Point(self.centroide['x'],self.centroide['y'])
|
|
@@ -274,36 +376,15 @@ class ParcelaCatastral:
|
|
|
274
376
|
return selected_polygon['Ptipo1'].iloc[0].get('val_tipo_m2')
|
|
275
377
|
|
|
276
378
|
@property
|
|
277
|
-
def numero_plantas(self):
|
|
278
|
-
"""
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
- Se devuelve también el máximo bajo rasante
|
|
286
|
-
|
|
287
|
-
Parameters
|
|
288
|
-
----------
|
|
289
|
-
refcat : str
|
|
290
|
-
Referencia catastral completa (ej: '9795702WG1499N0001AY')
|
|
291
|
-
timeout : int
|
|
292
|
-
Timeout de la petición HTTP en segundos
|
|
293
|
-
|
|
294
|
-
Returns
|
|
295
|
-
-------
|
|
296
|
-
dict
|
|
297
|
-
{
|
|
298
|
-
'refcat': str,
|
|
299
|
-
'parts': int,
|
|
300
|
-
'max_above_ground': int | None,
|
|
301
|
-
'max_below_ground': int,
|
|
302
|
-
'total_floors': int | None,
|
|
303
|
-
'parts_detail': list
|
|
304
|
-
}
|
|
379
|
+
def numero_plantas(self) -> Dict[str, Any]:
|
|
380
|
+
"""Obtiene el número de plantas de un edificio a partir de su referencia catastral.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Diccionario con:
|
|
384
|
+
- 'plantas': Máximo número de plantas sobre rasante (o None)
|
|
385
|
+
- 'sotanos': Máximo número de plantas bajo rasante
|
|
386
|
+
- 'total': Suma de plantas sobre y bajo rasante (o None)
|
|
305
387
|
"""
|
|
306
|
-
|
|
307
388
|
if self.tipo == 'Rústico':
|
|
308
389
|
return 0
|
|
309
390
|
|
|
@@ -355,7 +436,6 @@ class ParcelaCatastral:
|
|
|
355
436
|
})
|
|
356
437
|
|
|
357
438
|
return {
|
|
358
|
-
"partes": parts_detail,
|
|
359
439
|
"plantas": max(above_floors) if above_floors else None,
|
|
360
440
|
"sotanos": max(below_floors) if below_floors else 0,
|
|
361
441
|
"total": (
|
|
@@ -489,7 +569,7 @@ class ParcelaCatastral:
|
|
|
489
569
|
"""
|
|
490
570
|
converters.to_parquet([self], filename)
|
|
491
571
|
|
|
492
|
-
class MetaParcela:
|
|
572
|
+
class MetaParcela(ParcelaHelper):
|
|
493
573
|
"""
|
|
494
574
|
Clase que representa una MetaParcela, es decir, una gran parcela catastral con
|
|
495
575
|
varias referencias catastrales (Parcelas Catastrales más pequeñas).
|
|
@@ -510,45 +590,71 @@ class MetaParcela:
|
|
|
510
590
|
|
|
511
591
|
"""
|
|
512
592
|
|
|
513
|
-
def __create_from_rc(self, rc: str):
|
|
514
|
-
"""Create an instance of
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
593
|
+
def __create_from_rc(self, rc: str) -> None:
|
|
594
|
+
"""Create an instance of MetaParcela from a RC (Referencia Catastral) string.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
rc: Referencia catastral de la metaparcela
|
|
598
|
+
|
|
599
|
+
Raises:
|
|
600
|
+
ErrorServidorCatastro: Si el servidor devuelve una respuesta vacía o error
|
|
601
|
+
"""
|
|
602
|
+
params = self._parametrizar_peticion(RefCat=rc)
|
|
603
|
+
response = self._llamar_a_api(f'{URL_BASE_CALLEJERO}/Consulta_DNPRC', params)
|
|
604
|
+
info_cadastre = self._parsear_respuesta(response)
|
|
605
|
+
|
|
606
|
+
if self._comprobar_errores_catastro(info_cadastre):
|
|
607
|
+
self.parcelas = []
|
|
608
|
+
num_parcelas = self._obtener_numero_parcelas(info_cadastre, 'dnprc')
|
|
609
|
+
for idx in range(num_parcelas):
|
|
610
|
+
rc_dict = info_cadastre.get('consulta_dnprcResult').get('lrcdnp').get('rcdnp')[idx].get('rc')
|
|
611
|
+
rc = self._extraer_rc_from_dict(rc_dict)
|
|
525
612
|
self.parcelas.append(ParcelaCatastral(rc=rc))
|
|
526
|
-
else:
|
|
527
|
-
raise ErrorServidorCatastro("El servidor ha devuelto una respuesta vacia")
|
|
528
613
|
|
|
529
614
|
|
|
530
|
-
def __create_from_parcel(self, provincia: Union[str,None], municipio: Union[str,None], poligono: Union[str,None], parcela: Union[str,None]):
|
|
531
|
-
"""Create an instance of
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
615
|
+
def __create_from_parcel(self, provincia: Union[str,None], municipio: Union[str,None], poligono: Union[str,None], parcela: Union[str,None]) -> None:
|
|
616
|
+
"""Create an instance of MetaParcela from parcel data.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
provincia: Provincia donde se encuentra la parcela
|
|
620
|
+
municipio: Municipio donde se encuentra la parcela
|
|
621
|
+
poligono: Número de polígono
|
|
622
|
+
parcela: Número de parcela
|
|
623
|
+
|
|
624
|
+
Raises:
|
|
625
|
+
ErrorServidorCatastro: Si el servidor devuelve una respuesta vacía o error
|
|
626
|
+
"""
|
|
627
|
+
params = self._parametrizar_peticion(
|
|
628
|
+
Provincia=provincia,
|
|
629
|
+
Municipio=municipio,
|
|
630
|
+
Poligono=poligono,
|
|
631
|
+
Parcela=parcela
|
|
632
|
+
)
|
|
633
|
+
response = self._llamar_a_api(f'{URL_BASE_CALLEJERO}/Consulta_DNPPP', params)
|
|
634
|
+
info_cadastre = self._parsear_respuesta(response)
|
|
635
|
+
|
|
636
|
+
if self._comprobar_errores_catastro(info_cadastre):
|
|
637
|
+
self.parcelas = []
|
|
638
|
+
num_parcelas = self._obtener_numero_parcelas(info_cadastre, 'dnppp')
|
|
639
|
+
for idx in range(num_parcelas):
|
|
640
|
+
rc_dict = info_cadastre.get('consulta_dnpppResult').get('lrcdnp').get('rcdnp')[idx].get('rc')
|
|
641
|
+
rc = self._extraer_rc_from_dict(rc_dict)
|
|
642
|
+
self.parcelas.append(ParcelaCatastral(rc=rc))
|
|
549
643
|
|
|
550
|
-
def __create_from_address(self, provincia: Union[str,None], municipio: Union[str,None], tipo_via: Union[str,None], calle: Union[str,None], numero: Union[str,None]):
|
|
551
|
-
"""Create an instance of
|
|
644
|
+
def __create_from_address(self, provincia: Union[str,None], municipio: Union[str,None], tipo_via: Union[str,None], calle: Union[str,None], numero: Union[str,None]) -> None:
|
|
645
|
+
"""Create an instance of MetaParcela from an address.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
provincia: Provincia donde se encuentra la dirección
|
|
649
|
+
municipio: Municipio donde se encuentra la dirección
|
|
650
|
+
tipo_via: Tipo de vía (calle, avenida, etc.)
|
|
651
|
+
calle: Nombre de la calle
|
|
652
|
+
numero: Número de la dirección
|
|
653
|
+
|
|
654
|
+
Raises:
|
|
655
|
+
ErrorServidorCatastro: Si el servidor devuelve una respuesta vacía o error
|
|
656
|
+
Exception: Si la calle no existe
|
|
657
|
+
"""
|
|
552
658
|
info_calle = Calle(
|
|
553
659
|
municipio=Municipio(
|
|
554
660
|
provincia=provincia,
|
|
@@ -559,29 +665,27 @@ class MetaParcela:
|
|
|
559
665
|
)
|
|
560
666
|
|
|
561
667
|
if info_calle:
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
668
|
+
params = self._parametrizar_peticion(
|
|
669
|
+
Provincia=info_calle.municipio.provincia,
|
|
670
|
+
Municipio=info_calle.municipio.municipio,
|
|
671
|
+
Sigla=info_calle.tipo_via,
|
|
672
|
+
Calle=info_calle.calle,
|
|
673
|
+
Numero=numero
|
|
674
|
+
)
|
|
675
|
+
response = self._llamar_a_api(f'{URL_BASE_CALLEJERO}/Consulta_DNPLOC', params)
|
|
676
|
+
info_cadastre = self._parsear_respuesta(response)
|
|
570
677
|
|
|
571
|
-
if
|
|
572
|
-
info_cadastre = json.loads(req.content)
|
|
678
|
+
if self._comprobar_errores_catastro(info_cadastre):
|
|
573
679
|
self.parcelas = []
|
|
574
|
-
num_parcelas =
|
|
680
|
+
num_parcelas = self._obtener_numero_parcelas(info_cadastre, 'dnploc')
|
|
575
681
|
for idx in range(num_parcelas):
|
|
576
|
-
|
|
682
|
+
rc_dict = info_cadastre.get('consulta_dnplocResult').get('lrcdnp').get('rcdnp')[idx].get('rc')
|
|
683
|
+
rc = self._extraer_rc_from_dict(rc_dict)
|
|
577
684
|
self.parcelas.append(ParcelaCatastral(rc=rc))
|
|
578
|
-
else:
|
|
579
|
-
raise ErrorServidorCatastro("El servidor ha devuelto una respuesta vacia")
|
|
580
|
-
|
|
581
685
|
else:
|
|
582
686
|
raise Exception('La calle no existe.')
|
|
583
687
|
|
|
584
|
-
def __init__(self, rc: Union[str,None] = None, provincia: Union[int,str,None] = None, municipio: Union[int,str,None] = None, poligono: Union[int,None] = None, parcela: Union[int,None] = None, tipo_via: Union[str,None] = None, calle: Union[str,None] = None, numero: Union[str,None] = None):
|
|
688
|
+
def __init__(self, rc: Union[str,None] = None, provincia: Union[int,str,None] = None, municipio: Union[int,str,None] = None, poligono: Union[int,None] = None, parcela: Union[int,None] = None, tipo_via: Union[str,None] = None, calle: Union[str,None] = None, numero: Union[str,None] = None) -> None:
|
|
585
689
|
if rc:
|
|
586
690
|
self.rc = rc
|
|
587
691
|
self.__create_from_rc(rc)
|
|
@@ -602,8 +706,7 @@ class MetaParcela:
|
|
|
602
706
|
|
|
603
707
|
|
|
604
708
|
def to_dataframe(self):
|
|
605
|
-
"""
|
|
606
|
-
Convierte la MetaParcela en un DataFrame de pandas.
|
|
709
|
+
"""Convierte la MetaParcela en un DataFrame de pandas.
|
|
607
710
|
|
|
608
711
|
Returns:
|
|
609
712
|
pd.DataFrame: Un DataFrame que contiene las parcelas de la MetaParcela.
|
|
@@ -611,11 +714,10 @@ class MetaParcela:
|
|
|
611
714
|
return converters.to_geodataframe(self.parcelas)
|
|
612
715
|
|
|
613
716
|
def to_json(self, filename: Union[str,None] = None) -> str:
|
|
614
|
-
"""
|
|
615
|
-
Convierte la MetaParcela en un JSON.
|
|
717
|
+
"""Convierte la MetaParcela en un JSON.
|
|
616
718
|
|
|
617
719
|
Args:
|
|
618
|
-
filename
|
|
720
|
+
filename: Nombre del archivo donde guardar el JSON. Defaults to None.
|
|
619
721
|
|
|
620
722
|
Returns:
|
|
621
723
|
str: Una cadena JSON que contiene las parcelas de la MetaParcela.
|
|
@@ -623,32 +725,29 @@ class MetaParcela:
|
|
|
623
725
|
return converters.to_json(self.parcelas, filename)
|
|
624
726
|
|
|
625
727
|
def to_csv(self, filename: Union[str,None] = None) -> str:
|
|
626
|
-
"""
|
|
627
|
-
Convierte la MetaParcela en un CSV.
|
|
728
|
+
"""Convierte la MetaParcela en un CSV.
|
|
628
729
|
|
|
629
730
|
Args:
|
|
630
|
-
filename
|
|
731
|
+
filename: Nombre del archivo donde guardar el CSV. Defaults to None.
|
|
631
732
|
|
|
632
733
|
Returns:
|
|
633
734
|
str: Una cadena CSV que contiene las parcelas de la MetaParcela.
|
|
634
735
|
"""
|
|
635
736
|
return converters.to_csv(self.parcelas, filename)
|
|
636
737
|
|
|
637
|
-
def to_shapefile(self, filename: str):
|
|
638
|
-
"""
|
|
639
|
-
Guarda la MetaParcela como un archivo Shapefile.
|
|
738
|
+
def to_shapefile(self, filename: str) -> None:
|
|
739
|
+
"""Guarda la MetaParcela como un archivo Shapefile.
|
|
640
740
|
|
|
641
741
|
Args:
|
|
642
|
-
filename
|
|
742
|
+
filename: El nombre del archivo Shapefile a guardar.
|
|
643
743
|
"""
|
|
644
744
|
converters.to_shapefile(self.parcelas, filename)
|
|
645
745
|
|
|
646
|
-
def to_parquet(self, filename: str):
|
|
647
|
-
"""
|
|
648
|
-
Guarda la MetaParcela como un archivo Parquet.
|
|
746
|
+
def to_parquet(self, filename: str) -> None:
|
|
747
|
+
"""Guarda la MetaParcela como un archivo Parquet.
|
|
649
748
|
|
|
650
749
|
Args:
|
|
651
|
-
filename
|
|
750
|
+
filename: El nombre del archivo Parquet a guardar.
|
|
652
751
|
"""
|
|
653
752
|
converters.to_parquet(self.parcelas, filename)
|
|
654
753
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ESCatastroLib
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Una librería de Python para consultar los datos del Catastro COMO OBJETOS. Incluye información geográfica.
|
|
5
5
|
Project-URL: Documentation, https://github.com/IvanitiX/ESCatastroLib#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/IvanitiX/ESCatastroLib/issues
|
|
@@ -17,12 +17,14 @@ Classifier: Operating System :: OS Independent
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Topic :: Software Development :: Build Tools
|
|
19
19
|
Requires-Dist: folium
|
|
20
|
-
Requires-Dist: geopandas
|
|
20
|
+
Requires-Dist: geopandas<0.14; python_version < '3.9'
|
|
21
|
+
Requires-Dist: geopandas>=0.14; python_version >= '3.9'
|
|
21
22
|
Requires-Dist: geopy
|
|
22
|
-
Requires-Dist: pyarrow
|
|
23
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: pyarrow<14; python_version < '3.9'
|
|
24
|
+
Requires-Dist: pyarrow>=14; python_version >= '3.9'
|
|
24
25
|
Requires-Dist: requests
|
|
25
|
-
Requires-Dist: shapely
|
|
26
|
+
Requires-Dist: shapely<2; python_version < '3.9'
|
|
27
|
+
Requires-Dist: shapely>=2; python_version >= '3.9'
|
|
26
28
|
Requires-Dist: xmltodict
|
|
27
29
|
Description-Content-Type: text/markdown
|
|
28
30
|
|
|
@@ -116,6 +118,34 @@ referencia a una MetaParcela.
|
|
|
116
118
|
* `regiones (list)`: Una lista de regiones en la parcela con una descripción y superficie.
|
|
117
119
|
* `centroide (dict)`: Las coordenadas del centroide de la parcela. (Latitud y longitud)
|
|
118
120
|
* `geometria (list)`: Una lista de puntos que representan la geometría de la parcela. (Latitud y longitud)
|
|
121
|
+
* `superficie_total (float)`: La superficie total de la parcela en metros cuadrados.
|
|
122
|
+
* `superficie_construida (float)`: La superficie construida en metros cuadrados.
|
|
123
|
+
* `superficie (float)`: La superficie total de las regiones en metros cuadrados.
|
|
124
|
+
|
|
125
|
+
**Métodos:**
|
|
126
|
+
|
|
127
|
+
* `distancias_aristas` (property): Calcula las distancias entre puntos consecutivos de la geometría de la parcela.
|
|
128
|
+
* Returns: `Optional[List[float]]` - Lista de distancias entre aristas consecutivas en metros, o None si no hay geometría.
|
|
129
|
+
|
|
130
|
+
* `perimetro` (property): Calcula el perímetro total de la geometría de la parcela.
|
|
131
|
+
* Returns: `Optional[float]` - Perímetro total en metros, o None si no hay geometría.
|
|
132
|
+
|
|
133
|
+
* `valor_catastral_urbano_m2(anio: int) -> Optional[float]`: Obtiene el valor catastral por metro cuadrado para parcelas urbanas.
|
|
134
|
+
* Args: `anio` (int) - Año del valor catastral a consultar.
|
|
135
|
+
* Returns: Valor catastral en €/m², 0 si es rústica, o None si hay algún error.
|
|
136
|
+
|
|
137
|
+
* `valor_catastral_rustico_m2(anio: str) -> Optional[Dict]`: Obtiene los valores catastrales de tierras para parcelas rústicas.
|
|
138
|
+
* Args: `anio` (str) - Año del valor catastral a consultar.
|
|
139
|
+
* Returns: Diccionario con `region`, `nombre_region` y `modulos_€/ha`, o None si no se encuentran.
|
|
140
|
+
|
|
141
|
+
* `numero_plantas` (property): Obtiene el número de plantas de un edificio.
|
|
142
|
+
* Returns: `Dict[str, Any]` con claves `plantas`, `sotanos` y `total`.
|
|
143
|
+
|
|
144
|
+
* `to_dataframe()`: Convierte la parcela en un GeoDataFrame de GeoPandas.
|
|
145
|
+
* `to_json(filename: Optional[str] = None) -> str`: Convierte la parcela a JSON.
|
|
146
|
+
* `to_csv(filename: Optional[str] = None) -> str`: Convierte la parcela a CSV.
|
|
147
|
+
* `to_shapefile(filename: str)`: Guarda la parcela como Shapefile.
|
|
148
|
+
* `to_parquet(filename: str)`: Guarda la parcela como Parquet.
|
|
119
149
|
|
|
120
150
|
|
|
121
151
|
## Clase MetaParcela
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
ESCatastroLib/__about__.py,sha256=
|
|
1
|
+
ESCatastroLib/__about__.py,sha256=buISjIv39OyjZ4jasw7ncClX_p6X5LIUq6vQCYP8qqc,128
|
|
2
2
|
ESCatastroLib/__init__.py,sha256=QCXZ5qTCAGhkZr63i0Y7zZpkLKd342edUzjt9U_Ujqs,220
|
|
3
3
|
ESCatastroLib/models/Calle.py,sha256=TfgJfRiCSl6Fgug2--Wo2x5cj6JQdhHQojtZDZOgVSo,2846
|
|
4
|
-
ESCatastroLib/models/InfoCatastral.py,sha256=
|
|
4
|
+
ESCatastroLib/models/InfoCatastral.py,sha256=szmAdCmFS_J2-IgBv5UuowAGwAPF5xhuKoRddNYv8xA,34485
|
|
5
5
|
ESCatastroLib/models/Municipio.py,sha256=J3X9G7EMc63651EDAMOqo846Ed-5cVjJfZLZeohPw74,1566
|
|
6
6
|
ESCatastroLib/models/__init__.py,sha256=Y8nN1Qp3_-VX7njuXwFWSq-62clHj878miEQGnzNXlc,149
|
|
7
7
|
ESCatastroLib/utils/__init__.py,sha256=kQj0ORkR_JMgwWgGi73APqn3p-UQipiuMY1QaBBmkLs,106
|
|
8
8
|
ESCatastroLib/utils/converters.py,sha256=JFRuFvIidRxVJitCPukHAcFsraIuqR-hfSaU0vleGfU,2705
|
|
9
9
|
ESCatastroLib/utils/exceptions.py,sha256=YM3AuKHZ61SYgpmtYFz9n7JavdEOq2mLwyk2VzIol5A,759
|
|
10
10
|
ESCatastroLib/utils/statics.py,sha256=YZ202WkreqsPKwhVxuNDVBWagobYWUIJdbh6vBw_b2s,5169
|
|
11
|
-
ESCatastroLib/utils/test_plantas.py,sha256=iCXxktsAnWDe8dniAsLWKBHFUKBQppf9GAzpE-_ut9c,3083
|
|
12
|
-
ESCatastroLib/utils/test_valor_catastral_tierras.py,sha256=Kjb-RiAPW0Sq4qBgk8BNd3cTM1cXi2vXSpCyMHLdvqk,4057
|
|
13
11
|
ESCatastroLib/utils/utils.py,sha256=mecrCnjlJX8dA1edmsRhEP_3MfIR68GFxtfTUfIHGEE,9218
|
|
14
|
-
escatastrolib-0.0.
|
|
15
|
-
escatastrolib-0.0.
|
|
16
|
-
escatastrolib-0.0.
|
|
17
|
-
escatastrolib-0.0.
|
|
12
|
+
escatastrolib-1.0.0.dist-info/METADATA,sha256=0ye6_LUFEsuTXaazqyeFRyv3LUxCPbLX8aKnA-MciuA,20050
|
|
13
|
+
escatastrolib-1.0.0.dist-info/WHEEL,sha256=aha0VrrYvgDJ3Xxl3db_g_MDIW-ZexDdrc_m-Hk8YY4,105
|
|
14
|
+
escatastrolib-1.0.0.dist-info/licenses/LICENSE,sha256=kgDY5GRu9WGoKk_0F0iYmXuLmGiZ9hgtReHhCfJBWr8,11319
|
|
15
|
+
escatastrolib-1.0.0.dist-info/RECORD,,
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
import xmltodict
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
WFS_URL = "https://ovc.catastro.meh.es/INSPIRE/wfsBU.aspx"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def get_building_floors_by_refcat(refcat, timeout=30):
|
|
9
|
-
"""
|
|
10
|
-
Obtiene el número de plantas de un edificio a partir de su
|
|
11
|
-
referencia catastral usando el WFS INSPIRE de Catastro.
|
|
12
|
-
|
|
13
|
-
Criterio:
|
|
14
|
-
- Se consultan las BuildingPart
|
|
15
|
-
- Se toma el máximo número de plantas sobre rasante
|
|
16
|
-
- Se devuelve también el máximo bajo rasante
|
|
17
|
-
|
|
18
|
-
Parameters
|
|
19
|
-
----------
|
|
20
|
-
refcat : str
|
|
21
|
-
Referencia catastral completa (ej: '9795702WG1499N0001AY')
|
|
22
|
-
timeout : int
|
|
23
|
-
Timeout de la petición HTTP en segundos
|
|
24
|
-
|
|
25
|
-
Returns
|
|
26
|
-
-------
|
|
27
|
-
dict
|
|
28
|
-
{
|
|
29
|
-
'refcat': str,
|
|
30
|
-
'parts': int,
|
|
31
|
-
'max_above_ground': int | None,
|
|
32
|
-
'max_below_ground': int,
|
|
33
|
-
'total_floors': int | None,
|
|
34
|
-
'parts_detail': list
|
|
35
|
-
}
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
params = {
|
|
39
|
-
"service": "WFS",
|
|
40
|
-
"version": "2.0.0",
|
|
41
|
-
"request": "GetFeature",
|
|
42
|
-
"STOREDQUERIE_ID": "GetBuildingPartByParcel",
|
|
43
|
-
"REFCAT": refcat,
|
|
44
|
-
"srsname": "EPSG:4326"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
response = requests.get(WFS_URL, params=params, timeout=timeout)
|
|
48
|
-
response.raise_for_status()
|
|
49
|
-
|
|
50
|
-
data = xmltodict.parse(response.content)
|
|
51
|
-
|
|
52
|
-
feature_collection = data.get("gml:FeatureCollection", {})
|
|
53
|
-
members = feature_collection.get("gml:featureMember", [])
|
|
54
|
-
|
|
55
|
-
if isinstance(members, dict):
|
|
56
|
-
members = [members]
|
|
57
|
-
|
|
58
|
-
above_floors = []
|
|
59
|
-
below_floors = []
|
|
60
|
-
parts_detail = []
|
|
61
|
-
|
|
62
|
-
for member in members:
|
|
63
|
-
bp = member.get("bu-ext2d:BuildingPart")
|
|
64
|
-
if not bp:
|
|
65
|
-
continue
|
|
66
|
-
|
|
67
|
-
part_id = bp.get("@gml:id")
|
|
68
|
-
|
|
69
|
-
above = bp.get("bu-ext2d:numberOfFloorsAboveGround")
|
|
70
|
-
below = bp.get("bu-ext2d:numberOfFloorsBelowGround")
|
|
71
|
-
|
|
72
|
-
above_i = int(above) if above is not None else None
|
|
73
|
-
below_i = int(below) if below is not None else 0
|
|
74
|
-
|
|
75
|
-
if above_i is not None:
|
|
76
|
-
above_floors.append(above_i)
|
|
77
|
-
below_floors.append(below_i)
|
|
78
|
-
|
|
79
|
-
parts_detail.append({
|
|
80
|
-
"id": part_id,
|
|
81
|
-
"floors_above_ground": above_i,
|
|
82
|
-
"floors_below_ground": below_i
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
"refcat": refcat,
|
|
87
|
-
"parts": len(parts_detail),
|
|
88
|
-
"max_above_ground": max(above_floors) if above_floors else None,
|
|
89
|
-
"max_below_ground": max(below_floors) if below_floors else 0,
|
|
90
|
-
"total_floors": (
|
|
91
|
-
max(above_floors) + max(below_floors)
|
|
92
|
-
if above_floors else None
|
|
93
|
-
),
|
|
94
|
-
"parts_detail": parts_detail
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# -------------------------------------------------------
|
|
99
|
-
# Ejemplo de uso
|
|
100
|
-
# -------------------------------------------------------
|
|
101
|
-
if __name__ == "__main__":
|
|
102
|
-
refcat = "6225302WG2662E0024ZJ"
|
|
103
|
-
result = get_building_floors_by_refcat(refcat)
|
|
104
|
-
|
|
105
|
-
print("Referencia:", result["refcat"])
|
|
106
|
-
print("Partes:", result["parts"])
|
|
107
|
-
print("Plantas sobre rasante:", result["max_above_ground"])
|
|
108
|
-
print("Plantas bajo rasante:", result["max_below_ground"])
|
|
109
|
-
print("Total plantas:", result["total_floors"])
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from escatastrolib import ParcelaCatastral
|
|
3
|
-
from pyproj import Transformer
|
|
4
|
-
|
|
5
|
-
WMS_URL = "https://www1.sedecatastro.gob.es/Cartografia/GeoServerWMS.aspx"
|
|
6
|
-
LAYER = "IAMIR26:athiamir26"
|
|
7
|
-
WIDTH = 101
|
|
8
|
-
HEIGHT = 101
|
|
9
|
-
I = WIDTH // 2
|
|
10
|
-
J = HEIGHT // 2
|
|
11
|
-
|
|
12
|
-
CULTIVOS = {
|
|
13
|
-
"ARZ": "Arrozal",
|
|
14
|
-
"CBP": "Cultivos bajo plástico",
|
|
15
|
-
"CIT": "Cítricos",
|
|
16
|
-
"FCR": "Frutal carnoso regadío",
|
|
17
|
-
"FCS": "Frutal carnoso secano",
|
|
18
|
-
"FSR": "Frutos secos regadío",
|
|
19
|
-
"FSS": "Frutos secos secano",
|
|
20
|
-
"HUE": "Huerta",
|
|
21
|
-
"OLR": "Olivar regadío",
|
|
22
|
-
"OLS": "Olivar secano",
|
|
23
|
-
"PLT": "Plátanos",
|
|
24
|
-
"SBT": "Cultivos tropicales y subtropicales",
|
|
25
|
-
"TAR": "Tierras arables regadío",
|
|
26
|
-
"TAS": "Tierras arables secano",
|
|
27
|
-
"VIP": "Viñedo parral",
|
|
28
|
-
"VIR": "Viñedo regadío",
|
|
29
|
-
"VIS": "Viñedo secano",
|
|
30
|
-
"VOL": "Viña olivar",
|
|
31
|
-
"PAR": "Pasto con arbolado",
|
|
32
|
-
"PRD": "Prados",
|
|
33
|
-
"PST": "Pastizal",
|
|
34
|
-
"CON": "Coníferas",
|
|
35
|
-
"FRL": "Frondosas de crecimiento lento",
|
|
36
|
-
"FRR": "Frondosas de crecimiento rápido",
|
|
37
|
-
"MTR": "Matorral"
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def obtener_valor_catastral_tierras(referencia_catastral):
|
|
42
|
-
"""
|
|
43
|
-
Obtiene los valores catastrales de tierras a partir de una referencia catastral.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
referencia_catastral (str): Referencia catastral de la parcela.
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
dict: Datos de la parcela con región y módulos €/ha, o None si no se encuentran.
|
|
50
|
-
"""
|
|
51
|
-
parcela = ParcelaCatastral(referencia_catastral)
|
|
52
|
-
geometria = parcela.geometria # EPSG:4381
|
|
53
|
-
|
|
54
|
-
# Transformar geometría EPSG:4381 → EPSG:3857
|
|
55
|
-
transformer = Transformer.from_crs("EPSG:4381", "EPSG:3857", always_xy=True)
|
|
56
|
-
xs_3857 = []
|
|
57
|
-
ys_3857 = []
|
|
58
|
-
for p in geometria:
|
|
59
|
-
x, y = transformer.transform(p["x"], p["y"])
|
|
60
|
-
xs_3857.append(x)
|
|
61
|
-
ys_3857.append(y)
|
|
62
|
-
|
|
63
|
-
# BBOX EPSG:3857
|
|
64
|
-
bbox = f"{min(xs_3857)},{min(ys_3857)},{max(xs_3857)},{max(ys_3857)}"
|
|
65
|
-
|
|
66
|
-
params = {
|
|
67
|
-
"SERVICE": "WMS",
|
|
68
|
-
"VERSION": "1.3.0",
|
|
69
|
-
"REQUEST": "GetFeatureInfo",
|
|
70
|
-
"LAYERS": LAYER,
|
|
71
|
-
"QUERY_LAYERS": LAYER,
|
|
72
|
-
"STYLES": "",
|
|
73
|
-
"CRS": "EPSG:3857",
|
|
74
|
-
"SRS": "EPSG:3857",
|
|
75
|
-
"BBOX": bbox,
|
|
76
|
-
"WIDTH": WIDTH,
|
|
77
|
-
"HEIGHT": HEIGHT,
|
|
78
|
-
"FORMAT": "image/png",
|
|
79
|
-
"TRANSPARENT": "true",
|
|
80
|
-
"I": I,
|
|
81
|
-
"J": J,
|
|
82
|
-
"INFO_FORMAT": "application/json"
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
r = requests.get(WMS_URL, params=params, timeout=15)
|
|
86
|
-
r.raise_for_status()
|
|
87
|
-
data = r.json()
|
|
88
|
-
|
|
89
|
-
if not data.get("features"):
|
|
90
|
-
return None
|
|
91
|
-
|
|
92
|
-
props = data["features"][0]["properties"]
|
|
93
|
-
|
|
94
|
-
modulos = {
|
|
95
|
-
"region": props.get("REGIONAL"),
|
|
96
|
-
"nombre_region": props.get("NOMBRE"),
|
|
97
|
-
"modulos_€/ha": {}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
for cod, desc in CULTIVOS.items():
|
|
101
|
-
val = props.get(cod)
|
|
102
|
-
if isinstance(val, (int, float)) and val > 0:
|
|
103
|
-
modulos["modulos_€/ha"][desc] = val
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
"referencia_catastral": referencia_catastral,
|
|
107
|
-
"provincia": parcela.provincia,
|
|
108
|
-
"municipio": parcela.municipio,
|
|
109
|
-
"superficie_m2": parcela.superficie,
|
|
110
|
-
"region": modulos["region"],
|
|
111
|
-
"nombre_region": modulos["nombre_region"],
|
|
112
|
-
"modulos_€/ha": modulos["modulos_€/ha"]
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# =========================================================
|
|
117
|
-
# EJEMPLO DE USO
|
|
118
|
-
# =========================================================
|
|
119
|
-
|
|
120
|
-
if __name__ == "__main__":
|
|
121
|
-
rc = "18030A014004610000RH" # ejemplo
|
|
122
|
-
resultado = obtener_valor_catastral_tierras(rc)
|
|
123
|
-
|
|
124
|
-
if resultado:
|
|
125
|
-
print(f"RC: {resultado['referencia_catastral']}")
|
|
126
|
-
print(f"{resultado['municipio']} ({resultado['provincia']})")
|
|
127
|
-
print(f"Superficie: {resultado['superficie_m2']:.0f} m²")
|
|
128
|
-
print(f"Región: {resultado['region']} – {resultado['nombre_region']}\n")
|
|
129
|
-
for uso, valor in sorted(
|
|
130
|
-
resultado["modulos_€/ha"].items(),
|
|
131
|
-
key=lambda x: x[1],
|
|
132
|
-
reverse=True
|
|
133
|
-
):
|
|
134
|
-
print(f"{uso}: {valor:,.0f} €/Ha")
|
|
135
|
-
else:
|
|
136
|
-
print("No se encontraron módulos catastrales para esta parcela.")
|
|
File without changes
|