feat(parcel-sync): full GPKG export workflow with UAT autocomplete, hero buttons, layer catalog

- Fix login button (return success instead of ok)
- Add UAT autocomplete with NFD-normalized search (3186 entries)
- Add export-bundle API: base mode (terenuri+cladiri) + magic mode (enriched parcels)
- Add export-layer-gpkg API: individual layer GPKG download
- Add gpkg-export service: ogr2ogr with GeoJSON fallback
- Add reproject service: EPSG:3844 projection support
- Add magic-mode methods to eterra-client (immApps, folosinte, immovableList, docs, parcelDetails)
- Rewrite UI: 3-tab layout (Export/Catalog/Search), progress tracking, phase trail
This commit is contained in:
AI Assistant
2026-03-06 06:53:49 +02:00
parent 7cdea66fa2
commit 09a24233bb
10 changed files with 15102 additions and 566 deletions
@@ -0,0 +1,57 @@
/**
* Coordinate reprojection between EPSG:3844 (Stereo 70) and EPSG:4326 (WGS84).
*/
import proj4 from "proj4";
import type {
GeoJsonFeatureCollection,
GeoJsonMultiPolygon,
GeoJsonPolygon,
} from "./esri-geojson";
const EPSG_3844_DEF =
"+proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 +x_0=500000 +y_0=500000 +ellps=GRS80 +units=m +no_defs";
const EPSG_4326_DEF = "+proj=longlat +datum=WGS84 +no_defs";
proj4.defs("EPSG:3844", EPSG_3844_DEF);
proj4.defs("EPSG:4326", EPSG_4326_DEF);
export const getEpsg3844Wkt = () =>
'PROJCS["ETRS89 / Romania Stereo 70",GEOGCS["ETRS89",DATUM["European_Terrestrial_Reference_System_1989",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6258"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4258"]],PROJECTION["Oblique_Stereographic"],PARAMETER["latitude_of_origin",46],PARAMETER["central_meridian",25],PARAMETER["scale_factor",0.99975],PARAMETER["false_easting",500000],PARAMETER["false_northing",500000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","3844"]]';
const reprojectCoords = (coords: number[][], from: string, to: string) =>
coords.map(([x, y]) => {
const [nx, ny] = proj4(from, to, [x!, y!]);
return [nx!, ny!];
});
const reprojectPolygon = (polygon: number[][][], from: string, to: string) =>
polygon.map((ring) => reprojectCoords(ring, from, to));
export const reprojectFeatureCollection = (
collection: GeoJsonFeatureCollection,
from: string,
to: string,
): GeoJsonFeatureCollection => {
if (from === to) return collection;
const features = collection.features.map((feature) => {
if (feature.geometry.type === "Polygon") {
const geometry: GeoJsonPolygon = {
type: "Polygon",
coordinates: reprojectPolygon(feature.geometry.coordinates, from, to),
};
return { ...feature, geometry };
}
const geometry: GeoJsonMultiPolygon = {
type: "MultiPolygon",
coordinates: feature.geometry.coordinates.map((polygon) =>
reprojectPolygon(polygon, from, to),
),
};
return { ...feature, geometry };
});
return { ...collection, features };
};