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.
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Iván V.R <IvanVR@protonmail.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = '0.0.1rc3'
4
+ __version__ = '0.0.1rc5'
@@ -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
- info_cadastre = json.loads(req1.content)
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
- info_cadastre = json.loads(req.content)
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
- info_cadastre = json.loads(req.content)
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
- "superficie": pc.superficie,
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]) ,
@@ -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.")
@@ -1,7 +1,9 @@
1
1
  import requests
2
2
  import json
3
3
 
4
- from typing import Union
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
- return {
204
- 'x': data.get('lng'),
205
- 'y': data.get('lat'),
206
- 'rc': data.get('refCatastral')
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.1rc3
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 escatastrolib import Municipio, Calle, ParcelaCatastral, MetaParcela
271
- from escatastrolib.utils import listar_provincias, listar_tipos_via, listar_calles, listar_municipios
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,,