Files
ArchiTools/src/modules/parcel-sync/services/esri-geojson.ts
T
AI Assistant ba579d75c1 feat(parcel-sync): include no-geometry rows in Magic GPKG + HAS_GEOMETRY column
- Magic GPKG (terenuri_magic.gpkg) now contains ALL records:
  rows with geometry render as polygons, rows without have null geom
  but still carry all attribute/enrichment data (QGIS shows them fine)
- Added HAS_GEOMETRY column to Magic GPKG fields (0 or 1)
- GPKG builder now supports includeNullGeometry option: splits features
  into spatial-first (creates table), then appends null-geom rows
- Base terenuri.gpkg / cladiri.gpkg unchanged (spatial only)
- CSV still has all records as before
- GeoJsonFeature type now allows null geometry
- Reproject: null geometry guard added
- UI text updated: no longer says 'Nu apar in GPKG'
2026-03-07 18:06:28 +02:00

83 lines
2.2 KiB
TypeScript

/**
* ESRI → GeoJSON conversion for eTerra features.
*/
import type { EsriFeature } from "./eterra-client";
export type GeoJsonPolygon = { type: "Polygon"; coordinates: number[][][] };
export type GeoJsonMultiPolygon = {
type: "MultiPolygon";
coordinates: number[][][][];
};
export type GeoJsonFeature = {
type: "Feature";
properties: Record<string, unknown>;
geometry: GeoJsonPolygon | GeoJsonMultiPolygon | null;
};
export type GeoJsonFeatureCollection = {
type: "FeatureCollection";
features: GeoJsonFeature[];
};
const ringArea = (ring: number[][]) => {
let area = 0;
for (let i = 0; i < ring.length - 1; i++) {
const curr = ring[i]!;
const next = ring[i + 1]!;
area += curr[0]! * next[1]! - next[0]! * curr[1]!;
}
return area / 2;
};
const isClockwise = (ring: number[][]) => ringArea(ring) < 0;
const closeRing = (ring: number[][]) => {
if (ring.length === 0) return ring;
const first = ring[0]!;
const last = ring[ring.length - 1]!;
return first[0] !== last[0] || first[1] !== last[1]
? [...ring, [first[0]!, first[1]!]]
: ring;
};
const ringsToPolygons = (rings: number[][][]) => {
const polygons: number[][][][] = [];
let current: number[][][] | null = null;
for (const ring of rings) {
const closed = closeRing(ring);
if (closed.length < 4) continue;
if (isClockwise(closed) || !current) {
if (current) polygons.push(current);
current = [closed];
} else {
current.push(closed);
}
}
if (current) polygons.push(current);
return polygons;
};
export const esriToGeojson = (
features: EsriFeature[],
): GeoJsonFeatureCollection => {
const geoFeatures: GeoJsonFeature[] = [];
for (const feature of features) {
const rings = feature.geometry?.rings;
if (!rings?.length) continue;
const polygons = ringsToPolygons(rings);
if (!polygons.length) continue;
const geometry: GeoJsonPolygon | GeoJsonMultiPolygon =
polygons.length === 1
? { type: "Polygon", coordinates: polygons[0]! }
: { type: "MultiPolygon", coordinates: polygons };
geoFeatures.push({
type: "Feature",
properties: feature.attributes ?? {},
geometry,
});
}
return { type: "FeatureCollection", features: geoFeatures };
};