Guilherme Silberfarb Costa commited on
Commit
02cb858
·
1 Parent(s): 4970cc0

Use pyshp fallback for portable shapefiles

Browse files
backend/app/core/shapefile_runtime.py CHANGED
@@ -30,17 +30,52 @@ def _build_transformer(source_crs: Any, target_crs: str | None):
30
  return None
31
 
32
 
33
- def load_vector_dataframe(shapefile_path: str | Path, *, target_crs: str | None = "EPSG:4326") -> pd.DataFrame:
34
- import fiona
35
  from shapely.geometry import shape
36
  from shapely.ops import transform as shapely_transform
37
 
38
- path = Path(shapefile_path).expanduser().resolve()
39
- if not path.exists():
40
- raise FileNotFoundError(f"Shapefile nao encontrado: {path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- rows: list[dict[str, Any]] = []
43
- with fiona.open(path) as source:
 
 
 
 
 
 
44
  transformer = _build_transformer(source.crs_wkt or source.crs, target_crs)
45
  for feature in source:
46
  properties = dict(feature.get("properties") or {})
@@ -50,8 +85,35 @@ def load_vector_dataframe(shapefile_path: str | Path, *, target_crs: str | None
50
  geometry = shape(geometry_raw)
51
  if transformer is not None:
52
  geometry = shapely_transform(transformer.transform, geometry)
53
- properties["geometry"] = geometry
54
- rows.append(properties)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  return pd.DataFrame(rows)
57
 
@@ -63,9 +125,7 @@ def load_vector_geojson(
63
  property_fields: tuple[str, ...] | list[str] | None = None,
64
  simplify_tolerance: float = 0.0,
65
  ) -> dict[str, Any]:
66
- import fiona
67
- from shapely.geometry import mapping, shape
68
- from shapely.ops import transform as shapely_transform
69
 
70
  path = Path(shapefile_path).expanduser().resolve()
71
  if not path.exists():
@@ -77,31 +137,24 @@ def load_vector_geojson(
77
  }
78
 
79
  wanted_fields = tuple(str(field).strip() for field in (property_fields or ()) if str(field).strip())
80
- with fiona.open(path) as source:
81
- transformer = _build_transformer(source.crs_wkt or source.crs, target_crs)
82
- for feature in source:
83
- properties_raw = dict(feature.get("properties") or {})
84
- geometry_raw = feature.get("geometry")
85
- if not geometry_raw:
86
- continue
87
-
88
- geometry = shape(geometry_raw)
89
- if transformer is not None:
90
- geometry = shapely_transform(transformer.transform, geometry)
91
- if simplify_tolerance and geometry is not None:
92
- geometry = geometry.simplify(float(simplify_tolerance), preserve_topology=True)
93
-
94
- properties = (
95
- {field: properties_raw.get(field) for field in wanted_fields if field in properties_raw}
96
- if wanted_fields
97
- else properties_raw
98
- )
99
- feature_collection["features"].append(
100
- {
101
- "type": "Feature",
102
- "properties": properties,
103
- "geometry": mapping(geometry),
104
- }
105
- )
106
 
107
  return feature_collection
 
30
  return None
31
 
32
 
33
+ def _iter_features_pyshp(shapefile_path: Path, *, target_crs: str | None) -> list[tuple[dict[str, Any], Any]]:
34
+ import shapefile
35
  from shapely.geometry import shape
36
  from shapely.ops import transform as shapely_transform
37
 
38
+ reader = shapefile.Reader(str(shapefile_path))
39
+ try:
40
+ field_names = [str(field[0]) for field in reader.fields[1:]]
41
+ source_crs = None
42
+ prj_path = shapefile_path.with_suffix(".prj")
43
+ if prj_path.exists():
44
+ try:
45
+ source_crs = prj_path.read_text(encoding="utf-8", errors="replace")
46
+ except Exception:
47
+ source_crs = prj_path.read_text(errors="replace")
48
+ transformer = _build_transformer(source_crs, target_crs)
49
+
50
+ features: list[tuple[dict[str, Any], Any]] = []
51
+ for shape_record in reader.iterShapeRecords():
52
+ values = list(shape_record.record)
53
+ properties = {
54
+ field_name: values[index] if index < len(values) else None
55
+ for index, field_name in enumerate(field_names)
56
+ }
57
+ geometry_raw = getattr(shape_record.shape, "__geo_interface__", None)
58
+ geometry = None
59
+ if geometry_raw and geometry_raw.get("coordinates"):
60
+ geometry = shape(geometry_raw)
61
+ if transformer is not None:
62
+ geometry = shapely_transform(transformer.transform, geometry)
63
+ features.append((properties, geometry))
64
+ return features
65
+ finally:
66
+ try:
67
+ reader.close()
68
+ except Exception:
69
+ pass
70
 
71
+
72
+ def _iter_features_fiona(shapefile_path: Path, *, target_crs: str | None) -> list[tuple[dict[str, Any], Any]]:
73
+ import fiona
74
+ from shapely.geometry import shape
75
+ from shapely.ops import transform as shapely_transform
76
+
77
+ features: list[tuple[dict[str, Any], Any]] = []
78
+ with fiona.open(shapefile_path) as source:
79
  transformer = _build_transformer(source.crs_wkt or source.crs, target_crs)
80
  for feature in source:
81
  properties = dict(feature.get("properties") or {})
 
85
  geometry = shape(geometry_raw)
86
  if transformer is not None:
87
  geometry = shapely_transform(transformer.transform, geometry)
88
+ features.append((properties, geometry))
89
+ return features
90
+
91
+
92
+ def _load_features(shapefile_path: Path, *, target_crs: str | None = "EPSG:4326") -> list[tuple[dict[str, Any], Any]]:
93
+ pyshp_error = None
94
+ try:
95
+ return _iter_features_pyshp(shapefile_path, target_crs=target_crs)
96
+ except Exception as exc:
97
+ pyshp_error = exc
98
+
99
+ try:
100
+ return _iter_features_fiona(shapefile_path, target_crs=target_crs)
101
+ except Exception:
102
+ if pyshp_error is not None:
103
+ raise pyshp_error
104
+ raise
105
+
106
+
107
+ def load_vector_dataframe(shapefile_path: str | Path, *, target_crs: str | None = "EPSG:4326") -> pd.DataFrame:
108
+ path = Path(shapefile_path).expanduser().resolve()
109
+ if not path.exists():
110
+ raise FileNotFoundError(f"Shapefile nao encontrado: {path}")
111
+
112
+ rows: list[dict[str, Any]] = []
113
+ for properties, geometry in _load_features(path, target_crs=target_crs):
114
+ row = dict(properties)
115
+ row["geometry"] = geometry
116
+ rows.append(row)
117
 
118
  return pd.DataFrame(rows)
119
 
 
125
  property_fields: tuple[str, ...] | list[str] | None = None,
126
  simplify_tolerance: float = 0.0,
127
  ) -> dict[str, Any]:
128
+ from shapely.geometry import mapping
 
 
129
 
130
  path = Path(shapefile_path).expanduser().resolve()
131
  if not path.exists():
 
137
  }
138
 
139
  wanted_fields = tuple(str(field).strip() for field in (property_fields or ()) if str(field).strip())
140
+ for properties_raw, geometry in _load_features(path, target_crs=target_crs):
141
+ if geometry is None:
142
+ continue
143
+
144
+ if simplify_tolerance:
145
+ geometry = geometry.simplify(float(simplify_tolerance), preserve_topology=True)
146
+
147
+ properties = (
148
+ {field: properties_raw.get(field) for field in wanted_fields if field in properties_raw}
149
+ if wanted_fields
150
+ else dict(properties_raw)
151
+ )
152
+ feature_collection["features"].append(
153
+ {
154
+ "type": "Feature",
155
+ "properties": properties,
156
+ "geometry": mapping(geometry),
157
+ }
158
+ )
 
 
 
 
 
 
 
159
 
160
  return feature_collection
backend/requirements.txt CHANGED
@@ -12,6 +12,7 @@ folium
12
  branca
13
  joblib
14
  openpyxl
 
15
  geopandas
16
  fiona
17
  gradio>=4.0
 
12
  branca
13
  joblib
14
  openpyxl
15
+ pyshp
16
  geopandas
17
  fiona
18
  gradio>=4.0
build/windows/mesa_frame_portable.spec CHANGED
@@ -31,6 +31,7 @@ hiddenimports += collect_submodules("app")
31
  hiddenimports += collect_submodules("fiona")
32
  hiddenimports += collect_submodules("pyproj")
33
  hiddenimports += collect_submodules("shapely")
 
34
  hiddenimports += [
35
  "uvicorn.loops.auto",
36
  "uvicorn.protocols.http.auto",
 
31
  hiddenimports += collect_submodules("fiona")
32
  hiddenimports += collect_submodules("pyproj")
33
  hiddenimports += collect_submodules("shapely")
34
+ hiddenimports += collect_submodules("shapefile")
35
  hiddenimports += [
36
  "uvicorn.loops.auto",
37
  "uvicorn.protocols.http.auto",