feat(parcel-sync): DXF export in ZIP + detailed tooltips on hero buttons
DXF Export: - Add gpkgToDxf() helper using ogr2ogr -f DXF (non-fatal fallback) - export-local: terenuri.dxf, cladiri.dxf, terenuri_magic.dxf in ZIP - export-bundle: same DXF files alongside GPKGs - Zero overhead — conversion runs locally from DB data, no eTerra calls Hero Button Tooltips: - Hover shows ZIP contents: layer names, entity counts, sync dates - Base tooltip: "GPKG + DXF per layer" - Magic tooltip: "GPKG + DXF + CSV complet + Raport calitate" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -175,3 +175,33 @@ export const buildGpkg = async (options: GpkgBuildOptions): Promise<Buffer> => {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
return buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a GPKG buffer to DXF using ogr2ogr.
|
||||
* Returns null if ogr2ogr is not available or conversion fails.
|
||||
*/
|
||||
export const gpkgToDxf = async (
|
||||
gpkgBuffer: Buffer,
|
||||
layerName: string,
|
||||
): Promise<Buffer | null> => {
|
||||
if (!hasOgr2Ogr()) return null;
|
||||
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "eterra-dxf-"));
|
||||
const gpkgPath = path.join(tmpDir, "input.gpkg");
|
||||
const dxfPath = path.join(tmpDir, `${layerName}.dxf`);
|
||||
|
||||
try {
|
||||
await fs.writeFile(gpkgPath, gpkgBuffer);
|
||||
await runOgr(
|
||||
["-f", "DXF", dxfPath, gpkgPath, layerName],
|
||||
{ ...process.env, OGR_CT_FORCE_TRADITIONAL_GIS_ORDER: "YES" },
|
||||
);
|
||||
const buffer = Buffer.from(await fs.readFile(dxfPath));
|
||||
return buffer;
|
||||
} catch {
|
||||
// DXF conversion failed — not critical
|
||||
return null;
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user