ESCatastroLib 0.0.1rc3__py2.py3-none-any.whl → 0.0.1rc5__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 +232 -5
- ESCatastroLib/utils/converters.py +2 -1
- ESCatastroLib/utils/statics.py +32 -1
- ESCatastroLib/utils/test_plantas.py +109 -0
- ESCatastroLib/utils/test_valor_catastral_tierras.py +136 -0
- ESCatastroLib/utils/utils.py +29 -6
- {escatastrolib-0.0.1rc3.dist-info → escatastrolib-0.0.1rc5.dist-info}/METADATA +5 -5
- escatastrolib-0.0.1rc5.dist-info/RECORD +17 -0
- escatastrolib-0.0.1rc3.dist-info/RECORD +0 -15
- {escatastrolib-0.0.1rc3.dist-info → escatastrolib-0.0.1rc5.dist-info}/WHEEL +0 -0
- {escatastrolib-0.0.1rc3.dist-info → escatastrolib-0.0.1rc5.dist-info}/licenses/LICENSE +0 -0
ESCatastroLib/__about__.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import json
|
|
3
3
|
import xmltodict
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
|
|
6
|
+
from shapely import Point
|
|
5
7
|
from typing import Union
|
|
8
|
+
from pyproj import Transformer
|
|
6
9
|
|
|
7
|
-
from ..utils.statics import URL_BASE_CALLEJERO, URL_BASE_GEOGRAFIA, URL_BASE_CROQUIS_DATOS
|
|
8
|
-
from ..utils.utils import comprobar_errores, listar_sistemas_referencia, lon_lat_from_coords_dict
|
|
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
|
|
11
|
+
from ..utils.utils import comprobar_errores, listar_sistemas_referencia, lon_lat_from_coords_dict, lat_lon_from_coords_dict, distancia_entre_dos_puntos_geograficos
|
|
9
12
|
from ..utils.exceptions import ErrorServidorCatastro
|
|
10
13
|
from ..utils import converters
|
|
11
14
|
from .Calle import Calle, Municipio
|
|
@@ -88,6 +91,7 @@ class ParcelaCatastral:
|
|
|
88
91
|
'y': parcel_geometry[2*idx]
|
|
89
92
|
} for idx in range(len(parcel_geometry)//2)
|
|
90
93
|
]
|
|
94
|
+
self.superficie_total = float(geometry.get('FeatureCollection').get('member').get('cp:CadastralParcel').get('cp:areaValue').get('#text'))
|
|
91
95
|
|
|
92
96
|
def __create_from_rc(self, rc: str, projection: str):
|
|
93
97
|
"""Create an instance of InfoCatastral from a RC (Referencia Catastral) string."""
|
|
@@ -95,7 +99,10 @@ class ParcelaCatastral:
|
|
|
95
99
|
params={'RefCat': rc})
|
|
96
100
|
|
|
97
101
|
if len(req1.content) > 0:
|
|
98
|
-
|
|
102
|
+
try:
|
|
103
|
+
info_cadastre = json.loads(req1.content)
|
|
104
|
+
except:
|
|
105
|
+
raise ErrorServidorCatastro(mensaje=f"El servidor no devuelve un JSON. Mensaje en bruto: {req1.content}")
|
|
99
106
|
if comprobar_errores(info_cadastre):
|
|
100
107
|
cudnp = info_cadastre.get("consulta_dnprcResult", {}).get("control", {}).get("cudnp", 1)
|
|
101
108
|
|
|
@@ -120,6 +127,7 @@ class ParcelaCatastral:
|
|
|
120
127
|
self.__create_regions(info_cadastre)
|
|
121
128
|
self.__create_geometry(projection)
|
|
122
129
|
|
|
130
|
+
self.superficie_construida = sum(float(region.get('superficie')) for region in self.regiones)
|
|
123
131
|
self.superficie = sum(float(region.get('superficie')) for region in self.regiones)
|
|
124
132
|
else:
|
|
125
133
|
raise ErrorServidorCatastro("El servidor ha devuelto una respuesta vacia")
|
|
@@ -134,7 +142,10 @@ class ParcelaCatastral:
|
|
|
134
142
|
'Parcela': parcela
|
|
135
143
|
})
|
|
136
144
|
if len(req.content) > 0:
|
|
137
|
-
|
|
145
|
+
try:
|
|
146
|
+
info_cadastre = json.loads(req.content)
|
|
147
|
+
except:
|
|
148
|
+
raise ErrorServidorCatastro(mensaje=f"El servidor no devuelve un JSON. Mensaje en bruto: {req1.content}")
|
|
138
149
|
if comprobar_errores(info_cadastre):
|
|
139
150
|
cudnp = info_cadastre.get("consulta_dnpppResult", {}).get("control", {}).get("cudnp", 1)
|
|
140
151
|
|
|
@@ -168,7 +179,10 @@ class ParcelaCatastral:
|
|
|
168
179
|
})
|
|
169
180
|
|
|
170
181
|
if req.status_code == 200 and len(req.content) > 0 and comprobar_errores(req.json()):
|
|
171
|
-
|
|
182
|
+
try:
|
|
183
|
+
info_cadastre = json.loads(req1.content)
|
|
184
|
+
except:
|
|
185
|
+
raise ErrorServidorCatastro(mensaje=f"El servidor no devuelve un JSON. Mensaje en bruto: {req1.content}")
|
|
172
186
|
cudnp = info_cadastre.get("consulta_dnplocResult", {}).get("control", {}).get("cudnp", 1)
|
|
173
187
|
|
|
174
188
|
if cudnp > 1:
|
|
@@ -210,6 +224,219 @@ class ParcelaCatastral:
|
|
|
210
224
|
else:
|
|
211
225
|
raise ValueError("No se ha proporcionado suficiente información para realizar la búsqueda")
|
|
212
226
|
|
|
227
|
+
@property
|
|
228
|
+
def distancias_aristas(self):
|
|
229
|
+
"""
|
|
230
|
+
Calcula las distancias entre dos puntos de la geometría, par a par.
|
|
231
|
+
"""
|
|
232
|
+
if self.geometria:
|
|
233
|
+
distancias = []
|
|
234
|
+
for idx in range(0, len(self.geometria)):
|
|
235
|
+
idx_0 = len(self.geometria)-1 if idx == 0 else idx-1
|
|
236
|
+
idx_f=idx
|
|
237
|
+
distancias.append(distancia_entre_dos_puntos_geograficos(
|
|
238
|
+
lat_lon_from_coords_dict(self.geometria[idx_0]),
|
|
239
|
+
lat_lon_from_coords_dict(self.geometria[idx_f])
|
|
240
|
+
))
|
|
241
|
+
return distancias
|
|
242
|
+
else:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def perimetro(self):
|
|
247
|
+
"""
|
|
248
|
+
Calcula el perímetro de la geometría
|
|
249
|
+
"""
|
|
250
|
+
distancias = self.distancias_aristas
|
|
251
|
+
if distancias:
|
|
252
|
+
return sum(distancias)
|
|
253
|
+
else:
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def valor_catastral_urbano_m2(self, anio):
|
|
257
|
+
if self.tipo == 'Rústico':
|
|
258
|
+
return 0
|
|
259
|
+
|
|
260
|
+
req = requests.get(f'{URL_BASE_MAPA_VALORES_URBANOS}',
|
|
261
|
+
params={
|
|
262
|
+
"huso":"4326",
|
|
263
|
+
"x":self.centroide['x'],
|
|
264
|
+
"y":self.centroide['y'],
|
|
265
|
+
"anyoZV":anio,
|
|
266
|
+
"suelo": "N",
|
|
267
|
+
"tipo_mapa":"vivienda"
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
values_map = converters.gpd.read_file(req.content)
|
|
271
|
+
centroide_point = Point(self.centroide['x'],self.centroide['y'])
|
|
272
|
+
selected_polygon = values_map[values_map.geometry.covers(centroide_point)]
|
|
273
|
+
|
|
274
|
+
return selected_polygon['Ptipo1'].iloc[0].get('val_tipo_m2')
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def numero_plantas(self):
|
|
278
|
+
"""
|
|
279
|
+
Obtiene el número de plantas de un edificio a partir de su
|
|
280
|
+
referencia catastral usando el WFS INSPIRE de Catastro.
|
|
281
|
+
|
|
282
|
+
Criterio:
|
|
283
|
+
- Se consultan las BuildingPart
|
|
284
|
+
- Se toma el máximo número de plantas sobre rasante
|
|
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
|
+
}
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
if self.tipo == 'Rústico':
|
|
308
|
+
return 0
|
|
309
|
+
|
|
310
|
+
params = {
|
|
311
|
+
"service": "WFS",
|
|
312
|
+
"version": "2.0.0",
|
|
313
|
+
"request": "GetFeature",
|
|
314
|
+
"STOREDQUERIE_ID": "GetBuildingPartByParcel",
|
|
315
|
+
"REFCAT": self.rc,
|
|
316
|
+
"srsname": "EPSG:4326"
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
response = requests.get(URL_BASE_WFS_EDIFICIOS, params=params)
|
|
320
|
+
response.raise_for_status()
|
|
321
|
+
|
|
322
|
+
data = xmltodict.parse(response.content)
|
|
323
|
+
|
|
324
|
+
feature_collection = data.get("gml:FeatureCollection", {})
|
|
325
|
+
members = feature_collection.get("gml:featureMember", [])
|
|
326
|
+
|
|
327
|
+
if isinstance(members, dict):
|
|
328
|
+
members = [members]
|
|
329
|
+
|
|
330
|
+
above_floors = []
|
|
331
|
+
below_floors = []
|
|
332
|
+
parts_detail = []
|
|
333
|
+
|
|
334
|
+
for member in members:
|
|
335
|
+
bp = member.get("bu-ext2d:BuildingPart")
|
|
336
|
+
if not bp:
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
part_id = bp.get("@gml:id")
|
|
340
|
+
|
|
341
|
+
above = bp.get("bu-ext2d:numberOfFloorsAboveGround")
|
|
342
|
+
below = bp.get("bu-ext2d:numberOfFloorsBelowGround")
|
|
343
|
+
|
|
344
|
+
above_i = int(above) if above is not None else None
|
|
345
|
+
below_i = int(below) if below is not None else 0
|
|
346
|
+
|
|
347
|
+
if above_i is not None:
|
|
348
|
+
above_floors.append(above_i)
|
|
349
|
+
below_floors.append(below_i)
|
|
350
|
+
|
|
351
|
+
parts_detail.append({
|
|
352
|
+
"id": part_id,
|
|
353
|
+
"floors_above_ground": above_i,
|
|
354
|
+
"floors_below_ground": below_i
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
"partes": parts_detail,
|
|
359
|
+
"plantas": max(above_floors) if above_floors else None,
|
|
360
|
+
"sotanos": max(below_floors) if below_floors else 0,
|
|
361
|
+
"total": (
|
|
362
|
+
max(above_floors) + max(below_floors)
|
|
363
|
+
if above_floors else None
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
def valor_catastral_rustico_m2(self, anio:str):
|
|
368
|
+
"""
|
|
369
|
+
Obtiene los valores catastrales de tierras a partir de una referencia catastral.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
referencia_catastral (str): Referencia catastral de la parcela.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
dict: Datos de la parcela con región y módulos €/ha, o None si no se encuentran.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
if self.tipo == "Urbano":
|
|
379
|
+
return {}
|
|
380
|
+
|
|
381
|
+
geometria = self.geometria
|
|
382
|
+
|
|
383
|
+
# Transformar geometría EPSG:4381 → EPSG:3857
|
|
384
|
+
transformer = Transformer.from_crs("EPSG:4381", "EPSG:3857", always_xy=True)
|
|
385
|
+
xs_3857 = []
|
|
386
|
+
ys_3857 = []
|
|
387
|
+
for p in geometria:
|
|
388
|
+
x, y = transformer.transform(p["x"], p["y"])
|
|
389
|
+
xs_3857.append(x)
|
|
390
|
+
ys_3857.append(y)
|
|
391
|
+
|
|
392
|
+
# BBOX EPSG:3857
|
|
393
|
+
bbox = f"{min(xs_3857)},{min(ys_3857)},{max(xs_3857)},{max(ys_3857)}"
|
|
394
|
+
|
|
395
|
+
params = {
|
|
396
|
+
"SERVICE": "WMS",
|
|
397
|
+
"VERSION": "1.3.0",
|
|
398
|
+
"REQUEST": "GetFeatureInfo",
|
|
399
|
+
"LAYERS": f"IAMIR{int(str(anio)[-2:])-1}:athiamir{int(str(anio)[-2:])-1}",
|
|
400
|
+
"QUERY_LAYERS": f"IAMIR{int(str(anio)[-2:])-1}:athiamir{int(str(anio)[-2:])-1}",
|
|
401
|
+
"STYLES": "",
|
|
402
|
+
"CRS": "EPSG:3857",
|
|
403
|
+
"SRS": "EPSG:3857",
|
|
404
|
+
"BBOX": bbox,
|
|
405
|
+
"WIDTH": 101,
|
|
406
|
+
"HEIGHT": 101,
|
|
407
|
+
"FORMAT": "image/png",
|
|
408
|
+
"TRANSPARENT": "true",
|
|
409
|
+
"I": 55,
|
|
410
|
+
"J": 55,
|
|
411
|
+
"INFO_FORMAT": "application/json"
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
r = requests.get(URL_BASE_MAPA_VALORES_RUSTICOS, params=params, timeout=15)
|
|
415
|
+
r.raise_for_status()
|
|
416
|
+
data = r.json()
|
|
417
|
+
|
|
418
|
+
if not data.get("features"):
|
|
419
|
+
return None
|
|
420
|
+
|
|
421
|
+
props = data["features"][0]["properties"]
|
|
422
|
+
|
|
423
|
+
modulos = {
|
|
424
|
+
"region": props.get("REGIONAL"),
|
|
425
|
+
"nombre_region": props.get("NOMBRE"),
|
|
426
|
+
"modulos_€/ha": {}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
for cod, desc in CULTIVOS.items():
|
|
430
|
+
val = props.get(cod)
|
|
431
|
+
if isinstance(val, (int, float)) and val > 0:
|
|
432
|
+
modulos["modulos_€/ha"][desc] = val
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
"region": modulos["region"],
|
|
436
|
+
"nombre_region": modulos["nombre_region"],
|
|
437
|
+
"modulos_€/ha": modulos["modulos_€/ha"]
|
|
438
|
+
}
|
|
439
|
+
|
|
213
440
|
|
|
214
441
|
def to_dataframe(self):
|
|
215
442
|
"""
|
|
@@ -17,7 +17,8 @@ def to_geodataframe(parcelas: list) -> gpd.GeoDataFrame:
|
|
|
17
17
|
return gpd.GeoDataFrame({
|
|
18
18
|
"rc": pc.rc,
|
|
19
19
|
"tipo": pc.tipo,
|
|
20
|
-
"
|
|
20
|
+
"superficie_total": pc.superficie_total,
|
|
21
|
+
"superficie_construida": pc.superficie_construida,
|
|
21
22
|
"provincia": pc.provincia,
|
|
22
23
|
"municipio": pc.municipio,
|
|
23
24
|
"regiones": ','.join([f"{reg.get('descripcion')} ({reg.get('superficie')} m^2)" for reg in pc.regiones]) ,
|
ESCatastroLib/utils/statics.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
URL_BASE_CALLEJERO = 'https://ovc.catastro.meh.es/OVCServWeb/OVCWcfCallejero/COVCCallejero.svc/json'
|
|
2
2
|
URL_BASE_COORDENADAS = 'https://ovc.catastro.meh.es/OVCServWeb/OVCWcfCallejero/COVCCoordenadas.svc/json'
|
|
3
3
|
URL_BASE_GEOGRAFIA = 'http://ovc.catastro.meh.es/INSPIRE/wfsCP.aspx'
|
|
4
|
+
URL_BASE_WFS_EDIFICIOS = "https://ovc.catastro.meh.es/INSPIRE/wfsBU.aspx"
|
|
4
5
|
URL_BASE_CROQUIS_DATOS = f'https://www1.sedecatastro.gob.es/CYCBienInmueble/SECImprimirCroquisYDatos.aspx'
|
|
5
6
|
URL_BASE_CARTOCIUDAD_GEOCODER = 'https://www.cartociudad.es/geocoder/api/geocoder'
|
|
7
|
+
URL_BASE_MAPA_VALORES_URBANOS = "https://www1.sedecatastro.gob.es/Cartografia/SECDameGeoJSON.aspx"
|
|
8
|
+
URL_BASE_MAPA_VALORES_RUSTICOS = "https://www1.sedecatastro.gob.es/Cartografia/GeoServerWMS.aspx"
|
|
6
9
|
|
|
7
10
|
MAPEOS_PROVINCIAS = {
|
|
8
11
|
'La Coruña': 'A Coruña',
|
|
@@ -138,4 +141,32 @@ SISTEMAS_REFERENCIA = {
|
|
|
138
141
|
'EPSG:23029': 'UTM huso 29N en ED50',
|
|
139
142
|
'EPSG:23030': 'UTM huso 30N en ED50',
|
|
140
143
|
'EPSG:23031': 'UTM huso 31N en ED50'
|
|
141
|
-
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
CULTIVOS = {
|
|
147
|
+
"ARZ": "Arrozal",
|
|
148
|
+
"CBP": "Cultivos bajo plástico",
|
|
149
|
+
"CIT": "Cítricos",
|
|
150
|
+
"FCR": "Frutal carnoso regadío",
|
|
151
|
+
"FCS": "Frutal carnoso secano",
|
|
152
|
+
"FSR": "Frutos secos regadío",
|
|
153
|
+
"FSS": "Frutos secos secano",
|
|
154
|
+
"HUE": "Huerta",
|
|
155
|
+
"OLR": "Olivar regadío",
|
|
156
|
+
"OLS": "Olivar secano",
|
|
157
|
+
"PLT": "Plátanos",
|
|
158
|
+
"SBT": "Cultivos tropicales y subtropicales",
|
|
159
|
+
"TAR": "Tierras arables regadío",
|
|
160
|
+
"TAS": "Tierras arables secano",
|
|
161
|
+
"VIP": "Viñedo parral",
|
|
162
|
+
"VIR": "Viñedo regadío",
|
|
163
|
+
"VIS": "Viñedo secano",
|
|
164
|
+
"VOL": "Viña olivar",
|
|
165
|
+
"PAR": "Pasto con arbolado",
|
|
166
|
+
"PRD": "Prados",
|
|
167
|
+
"PST": "Pastizal",
|
|
168
|
+
"CON": "Coníferas",
|
|
169
|
+
"FRL": "Frondosas de crecimiento lento",
|
|
170
|
+
"FRR": "Frondosas de crecimiento rápido",
|
|
171
|
+
"MTR": "Matorral"
|
|
172
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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"])
|
|
@@ -0,0 +1,136 @@
|
|
|
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.")
|
ESCatastroLib/utils/utils.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import json
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from geopy.distance import geodesic
|
|
5
|
+
|
|
6
|
+
from typing import Union, List
|
|
5
7
|
|
|
6
8
|
from .statics import URL_BASE_CALLEJERO, MAPEOS_PROVINCIAS, TIPOS_VIA, SISTEMAS_REFERENCIA, URL_BASE_COORDENADAS, URL_BASE_CARTOCIUDAD_GEOCODER
|
|
7
9
|
from .exceptions import lanzar_excepcion
|
|
@@ -200,14 +202,35 @@ def geocodificar_direccion(direccion: str, municipio: str = None):
|
|
|
200
202
|
|
|
201
203
|
if response.status_code == 200:
|
|
202
204
|
data = json.loads(response.content.decode('utf-8').replace('callback(', '').replace(')', ''))
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
205
|
+
print(data)
|
|
206
|
+
if type(data) == dict:
|
|
207
|
+
return {
|
|
208
|
+
'x': data.get('lng'),
|
|
209
|
+
'y': data.get('lat'),
|
|
210
|
+
'rc': data.get('refCatastral')
|
|
211
|
+
}
|
|
212
|
+
elif type(data) == list and len(data) > 0:
|
|
213
|
+
pc = data[0]
|
|
214
|
+
return {
|
|
215
|
+
'x': pc.get('lng'),
|
|
216
|
+
'y': pc.get('lat'),
|
|
217
|
+
'rc': pc.get('refCatastral')
|
|
218
|
+
}
|
|
219
|
+
else:
|
|
220
|
+
return None
|
|
208
221
|
else:
|
|
209
222
|
return None
|
|
210
223
|
|
|
211
224
|
def lon_lat_from_coords_dict(coords):
|
|
212
225
|
return float(coords["x"]), float(coords["y"])
|
|
213
226
|
|
|
227
|
+
def lat_lon_from_coords_dict(coords):
|
|
228
|
+
return float(coords['y']), float(coords['x'])
|
|
229
|
+
|
|
230
|
+
def distancia_entre_dos_puntos_geograficos(origen: List[float], destino: List[float]):
|
|
231
|
+
"""
|
|
232
|
+
Devuelve la distancia en metros entre dos puntos geográficos usando latitud y longitud.
|
|
233
|
+
¡OJO! Las tuplas deben ser latitud y longitud y no al revés.
|
|
234
|
+
"""
|
|
235
|
+
return geodesic(origen,destino).m
|
|
236
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ESCatastroLib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.1rc5
|
|
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
|
|
@@ -10,7 +10,6 @@ License: Apache License (2.0)
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: catastro,espana,gis,spain
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
|
-
Classifier: Framework :: Pytest
|
|
14
13
|
Classifier: Intended Audience :: Developers
|
|
15
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
15
|
Classifier: Natural Language :: Spanish
|
|
@@ -19,6 +18,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
19
18
|
Classifier: Topic :: Software Development :: Build Tools
|
|
20
19
|
Requires-Dist: folium
|
|
21
20
|
Requires-Dist: geopandas
|
|
21
|
+
Requires-Dist: geopy
|
|
22
22
|
Requires-Dist: pyarrow
|
|
23
23
|
Requires-Dist: pytest
|
|
24
24
|
Requires-Dist: requests
|
|
@@ -267,8 +267,8 @@ Un archivo con el `filename`.
|
|
|
267
267
|
¡Buena pregunta! Os dejo algunos ejemplos:
|
|
268
268
|
|
|
269
269
|
```python
|
|
270
|
-
from
|
|
271
|
-
from
|
|
270
|
+
from ESCatastroLib import Municipio, Calle, ParcelaCatastral, MetaParcela
|
|
271
|
+
from ESCatastroLib.utils import listar_provincias, listar_tipos_via, listar_calles, listar_municipios
|
|
272
272
|
|
|
273
273
|
print(listar_provincias())
|
|
274
274
|
# > ['A CORUÑA', 'ALACANT', 'ALBACETE', 'ALMERIA', 'ASTURIAS', 'AVILA', 'BADAJOZ', ...]
|
|
@@ -364,4 +364,4 @@ Sigue las instrucciones de CONTRIBUTING.md y el Código de Conducta.
|
|
|
364
364
|
|
|
365
365
|
[1]: https://github.com/gisce/pycatastro
|
|
366
366
|
[2]: https://github.com/astrojuanlu
|
|
367
|
-
[3]: https://github.com/JaimeObregon
|
|
367
|
+
[3]: https://github.com/JaimeObregon
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
ESCatastroLib/__about__.py,sha256=4Gd4-yBRspaqdiFVWu4WyG_F7tAMkrybx_WdUxj5x1U,131
|
|
2
|
+
ESCatastroLib/__init__.py,sha256=QCXZ5qTCAGhkZr63i0Y7zZpkLKd342edUzjt9U_Ujqs,220
|
|
3
|
+
ESCatastroLib/models/Calle.py,sha256=TfgJfRiCSl6Fgug2--Wo2x5cj6JQdhHQojtZDZOgVSo,2846
|
|
4
|
+
ESCatastroLib/models/InfoCatastral.py,sha256=IJGrx0QAtfPSeXDss24lSorGMsyfwiWu6S2PChqCyY0,31362
|
|
5
|
+
ESCatastroLib/models/Municipio.py,sha256=J3X9G7EMc63651EDAMOqo846Ed-5cVjJfZLZeohPw74,1566
|
|
6
|
+
ESCatastroLib/models/__init__.py,sha256=Y8nN1Qp3_-VX7njuXwFWSq-62clHj878miEQGnzNXlc,149
|
|
7
|
+
ESCatastroLib/utils/__init__.py,sha256=kQj0ORkR_JMgwWgGi73APqn3p-UQipiuMY1QaBBmkLs,106
|
|
8
|
+
ESCatastroLib/utils/converters.py,sha256=JFRuFvIidRxVJitCPukHAcFsraIuqR-hfSaU0vleGfU,2705
|
|
9
|
+
ESCatastroLib/utils/exceptions.py,sha256=YM3AuKHZ61SYgpmtYFz9n7JavdEOq2mLwyk2VzIol5A,759
|
|
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
|
+
ESCatastroLib/utils/utils.py,sha256=mecrCnjlJX8dA1edmsRhEP_3MfIR68GFxtfTUfIHGEE,9218
|
|
14
|
+
escatastrolib-0.0.1rc5.dist-info/METADATA,sha256=dadx4qWDYpI9OWMy9mGOez1pbKcBTYzbW3lJ2482e8Y,18056
|
|
15
|
+
escatastrolib-0.0.1rc5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
16
|
+
escatastrolib-0.0.1rc5.dist-info/licenses/LICENSE,sha256=kgDY5GRu9WGoKk_0F0iYmXuLmGiZ9hgtReHhCfJBWr8,11319
|
|
17
|
+
escatastrolib-0.0.1rc5.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
ESCatastroLib/__about__.py,sha256=vsaWW_x-_9_aeEMTHUfLee5bPKUCtQ2XFAWd6MBIpyQ,131
|
|
2
|
-
ESCatastroLib/__init__.py,sha256=QCXZ5qTCAGhkZr63i0Y7zZpkLKd342edUzjt9U_Ujqs,220
|
|
3
|
-
ESCatastroLib/models/Calle.py,sha256=TfgJfRiCSl6Fgug2--Wo2x5cj6JQdhHQojtZDZOgVSo,2846
|
|
4
|
-
ESCatastroLib/models/InfoCatastral.py,sha256=H8Eo96OThC_OSnpQ7Jeuwo4We5f1XZ4C51lbvcDUSq4,23308
|
|
5
|
-
ESCatastroLib/models/Municipio.py,sha256=J3X9G7EMc63651EDAMOqo846Ed-5cVjJfZLZeohPw74,1566
|
|
6
|
-
ESCatastroLib/models/__init__.py,sha256=Y8nN1Qp3_-VX7njuXwFWSq-62clHj878miEQGnzNXlc,149
|
|
7
|
-
ESCatastroLib/utils/__init__.py,sha256=kQj0ORkR_JMgwWgGi73APqn3p-UQipiuMY1QaBBmkLs,106
|
|
8
|
-
ESCatastroLib/utils/converters.py,sha256=cxjesg23z0gxhaquuPaEQjhgb2Gnj5Ip2BqX_n3wY0s,2621
|
|
9
|
-
ESCatastroLib/utils/exceptions.py,sha256=YM3AuKHZ61SYgpmtYFz9n7JavdEOq2mLwyk2VzIol5A,759
|
|
10
|
-
ESCatastroLib/utils/statics.py,sha256=PpHPS-lopMYWg5pJeeCU5NtIJU6nUpeh9njFFKwfVfc,3958
|
|
11
|
-
ESCatastroLib/utils/utils.py,sha256=WdtW7NqkFH0erkqRXbPAQRJQmJXezSjbD6mmJK8OZ-4,8436
|
|
12
|
-
escatastrolib-0.0.1rc3.dist-info/METADATA,sha256=IyW6cvq6iaKl3Ej55r1cL3TnNLfNhHzxfLWhI2hkI4w,18066
|
|
13
|
-
escatastrolib-0.0.1rc3.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
14
|
-
escatastrolib-0.0.1rc3.dist-info/licenses/LICENSE,sha256=kgDY5GRu9WGoKk_0F0iYmXuLmGiZ9hgtReHhCfJBWr8,11319
|
|
15
|
-
escatastrolib-0.0.1rc3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|