@agencebio/cartobio-types
Advanced tools
Comparing version 1.2.1 to 1.3.0
@@ -1,3 +0,3 @@ | ||
import {EventType} from "../history"; | ||
import {CartoBioUser} from "../../providers/types/cartobio"; | ||
import {EventType} from "../history.js"; | ||
import type {CartoBioUser} from "../../providers/types/cartobio.d.ts"; | ||
@@ -4,0 +4,0 @@ export type HistoryEntry = { |
@@ -1,3 +0,3 @@ | ||
import {AgenceBioAdresseGeo, OrganismeCertificateur} from "../../providers/types/agence-bio"; | ||
import {NormalizedRecord} from "./record"; | ||
import type {AgenceBioAdresseGeo, OrganismeCertificateur} from "../../providers/types/agence-bio.d.ts"; | ||
import type {NormalizedRecord} from "./record.d.ts"; | ||
@@ -26,2 +26,2 @@ export type AgenceBioNormalizedOperator = { | ||
// (we should not have to different objects for sending operator + record) | ||
export type AgenceBioNormalizedOperatorWithRecord = AgenceBioNormalizedOperator & (NormalizedRecord | { metadata: any }) | ||
export type AgenceBioNormalizedOperatorWithRecord = AgenceBioNormalizedOperator & (NormalizedRecord | { metadata: any }); |
@@ -1,4 +0,4 @@ | ||
import {DBOperatorRecord} from "../../providers/types/cartobio"; | ||
import {AgenceBioNormalizedOperator} from "./operator"; | ||
import {CartoBioFeatureCollection} from "./features"; | ||
import type {DBOperatorRecord} from "../../providers/types/cartobio.d.ts"; | ||
import type {AgenceBioNormalizedOperator} from "./operator.d.ts"; | ||
import type {CartoBioFeatureCollection} from "./features.d.ts"; | ||
@@ -9,5 +9,8 @@ /** | ||
export type NormalizedRecord = Omit<DBOperatorRecord, 'parcelles'> & { | ||
parcelles: CartoBioFeatureCollection; | ||
operator: AgenceBioNormalizedOperator; | ||
parcelles: CartoBioFeatureCollection ; | ||
operator?: AgenceBioNormalizedOperator; | ||
}; | ||
export type NormalizedRecordSummary = Omit<NormalizedRecord, 'parcelles'> & { | ||
parcelles: number | ||
}; |
{ | ||
"name": "@agencebio/cartobio-types", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"main": "types.d.ts", | ||
@@ -5,0 +5,0 @@ "repository": { |
@@ -74,3 +74,3 @@ 'use strict' | ||
/* eslint-disable-next-line quotes */ | ||
const recordFields = /* sqlFragment */`record_id, numerobio, certification_date_debut, certification_date_fin, certification_state, created_at, updated_at, metadata, audit_history, audit_notes, audit_demandes` | ||
const recordFields = /* sqlFragment */`record_id, numerobio, certification_date_debut, certification_date_fin, certification_state, created_at, updated_at, metadata, audit_date, audit_history, audit_notes, audit_demandes` | ||
@@ -157,3 +157,3 @@ /** | ||
SELECT jsonb_agg((SELECT new_cultures || jsonb_build_object('variete', old_cultures->>'variete'))) AS cultures | ||
FROM previous_version, jsonb_array_elements($5::jsonb) AS new_cultures | ||
FROM previous_version, jsonb_array_elements($5::jsonb) AS new_cultures | ||
LEFT JOIN jsonb_array_elements(previous_version.cultures) AS old_cultures | ||
@@ -867,6 +867,9 @@ ON (new_cultures->>'CPF') = (old_cultures->>'CPF') | ||
/* sql */` | ||
SELECT COUNT(cartobio_parcelles.*) | ||
SELECT | ||
COUNT(cartobio_parcelles.*) as parcelles, | ||
COUNT(DISTINCT cartobio_operators.record_id) as parcellaires, | ||
SUM(ST_Area(ST_Transform(cartobio_parcelles.geometry, 4326)::geography)/10000) as surface | ||
FROM cartobio_operators | ||
JOIN cartobio_parcelles ON cartobio_operators.record_id = cartobio_parcelles.record_id | ||
WHERE cartobio_operators.metadata ->> 'source' != '';` | ||
JOIN cartobio_parcelles ON cartobio_operators.record_id = cartobio_parcelles.record_id | ||
WHERE cartobio_operators.metadata ->> 'source' != '' AND cartobio_operators.certification_state = 'CERTIFIED' AND ST_IsValid(cartobio_parcelles.geometry);` | ||
) | ||
@@ -873,0 +876,0 @@ |
@@ -43,2 +43,3 @@ const AdmZip = require('adm-zip') | ||
const id = getRandomFeatureId() | ||
const secondaryFields = secondaryCultures.filter(({ MainPlotId }) => MainPlotId === Field.Id) | ||
@@ -54,6 +55,3 @@ return Feature( | ||
cultureFromField(Field), | ||
...(secondaryCultures | ||
.filter(({ MainPlotId }) => MainPlotId === Field.Id) | ||
.map(cultureFromField) | ||
) | ||
...secondaryFields.map(cultureFromField) | ||
].filter(d => d), | ||
@@ -63,3 +61,6 @@ NUMERO_I: Field.IsletNum, | ||
conversion_niveau: '', | ||
commentaires: Field.Comment || '' | ||
commentaires: [Field, ...secondaryFields] | ||
.map(({ Comment }) => Comment) | ||
.filter(d => d) | ||
.join('\n\n') | ||
}, | ||
@@ -66,0 +67,0 @@ { id } |
@@ -5,2 +5,3 @@ const geo = require('verrazzano') | ||
const { Readable } = require('stream') | ||
const { XMLParser } = require('fast-xml-parser') | ||
const { getRandomFeatureId } = require('../outputs/features.js') | ||
@@ -11,2 +12,3 @@ const { obj } = require('through2') | ||
const { fromCodePacStrict } = require('@agencebio/rosetta-cultures') | ||
const { featureCollection, feature: Feature } = require('@turf/helpers') | ||
const intersect = require('@turf/intersect').default | ||
@@ -26,2 +28,28 @@ | ||
const projections = { | ||
// https://epsg.io/2154 | ||
metropole: '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs', | ||
// https://epsg.io/5490 | ||
antilles: '+proj=utm +zone=20 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs', | ||
// https://epsg.io/2975 | ||
reunion: '+proj=utm +zone=40 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs', | ||
// https://epsg.io/2972 | ||
guyane: '+proj=utm +zone=22 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs', | ||
// https://epsg.io/4471 | ||
mayotte: '+proj=utm +zone=38 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs' | ||
} | ||
const removeEmptyElements = (item) => item | ||
/** | ||
* @typedef {import('geojson').FeatureCollection} FeatureCollection | ||
* @typedef {import('@fastify/multipart').MultipartFile} MultipartFile | ||
*/ | ||
/** | ||
* Parse a Telepac Shapefile archive to GeoJSON | ||
* | ||
* @param {Promise<MultipartFile>} file | ||
* @returns {Promise<FeatureCollection>} | ||
*/ | ||
async function parseShapefileArchive (file) { | ||
@@ -31,15 +59,2 @@ const data = await file | ||
const projections = { | ||
// https://epsg.io/2154 | ||
metropole: '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs', | ||
// https://epsg.io/5490 | ||
antilles: '+proj=utm +zone=20 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs', | ||
// https://epsg.io/2975 | ||
reunion: '+proj=utm +zone=40 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs', | ||
// https://epsg.io/2972 | ||
guyane: '+proj=utm +zone=22 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs', | ||
// https://epsg.io/4471 | ||
mayotte: '+proj=utm +zone=38 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs' | ||
} | ||
const geojson = await getStream(Readable.from(buffer) | ||
@@ -85,7 +100,100 @@ .pipe(geo.from('shp')) | ||
return null | ||
}).find(a => a) | ||
}).find(removeEmptyElements) | ||
} | ||
/** | ||
* | ||
* @param {string} gmlCoordinates | ||
* @return {import('geojson').Position[]} | ||
*/ | ||
function toGeoJSONCoordinates (gmlCoordinates) { | ||
return gmlCoordinates.trim().replace(/\n/g, '').split(' ') | ||
// clean tabular spacing | ||
.filter(removeEmptyElements) | ||
// we split the single unit of X,Y into array pairs of [X, Y] | ||
.map(unit => unit.split(',')) | ||
// turn them into floats | ||
.map(([X, Y]) => [parseFloat(X), parseFloat(Y)]) | ||
} | ||
/** | ||
* | ||
* @see https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6 | ||
* @param {Object} gmlPolygon | ||
* @returns {import('geojson').Polygon} | ||
*/ | ||
function gmlGeometryToGeoJSONGeometry (gmlPolygon) { | ||
return { | ||
type: 'Polygon', | ||
coordinates: [ | ||
// exterior ring | ||
toGeoJSONCoordinates(gmlPolygon['gml:outerBoundaryIs']['gml:LinearRing']['gml:coordinates']), | ||
// optional interior rings | ||
...(gmlPolygon['gml:innerBoundaryIs'] ?? []).map((boundary) => { | ||
return toGeoJSONCoordinates(boundary['gml:LinearRing']['gml:coordinates']) | ||
}) | ||
].filter(removeEmptyElements) | ||
} | ||
} | ||
/** | ||
* Parse a Telepac/MesParcelles XML export to GeoJSON | ||
* For now, it works only with Metropole projection | ||
* | ||
* @param {Promise<MultipartFile>} file | ||
* @returns {Promise<FeatureCollection>} | ||
*/ | ||
async function parseTelepacXML (file) { | ||
const data = await file | ||
const xmlContent = (await data.toBuffer()).toString() | ||
const xml = new XMLParser({ | ||
ignoreAttributes: false, | ||
parseTagValue: false, | ||
isArray: (name, jpath, isLeafNode, isAttribute) => ['ilot', 'parcelle', 'gml:innerBoundaryIs'].includes(name) && !isAttribute && !isLeafNode | ||
}).parse(xmlContent) | ||
const PACAGE = xml.producteurs.producteur['@_numero-pacage'] | ||
const ilots = xml.producteurs.producteur.rpg.ilot ?? xml.producteurs.producteur.rpg.ilots.ilot | ||
const geojson = featureCollection(ilots.flatMap(ilot => { | ||
const { '@_numero-ilot': NUMERO_I, commune: COMMUNE } = ilot | ||
return ilot.parcelles.parcelle.map(parcelle => { | ||
const id = getRandomFeatureId() | ||
const props = parcelle['descriptif-parcelle'] | ||
const { '@_numero-parcelle': NUMERO_P } = props | ||
const TYPE = props['culture-principale']['code-culture'] | ||
const { '@_conduite-bio': AGRIBIO } = props['agri-bio'] ?? {} | ||
// const CODE_VAR = props['culture-principale']['precision'] ?? null | ||
return Feature( | ||
gmlGeometryToGeoJSONGeometry(parcelle.geometrie['gml:Polygon']), | ||
{ | ||
id, | ||
remoteId: `${NUMERO_I}.${NUMERO_P}`, | ||
COMMUNE, | ||
cultures: [ | ||
{ | ||
id: randomUUID(), | ||
CPF: fromCodePacStrict(TYPE/*, CODE_VAR */)?.code_cpf, | ||
TYPE | ||
} | ||
], | ||
NUMERO_I, | ||
NUMERO_P, | ||
PACAGE, | ||
conversion_niveau: AGRIBIO === 'true' ? 'AB?' : EtatProduction.NB | ||
}, | ||
{ id } | ||
) | ||
}) | ||
})) | ||
return toWgs84(geojson, projections.metropole) | ||
} | ||
module.exports = { | ||
parseShapefileArchive | ||
parseShapefileArchive, | ||
parseTelepacXML | ||
} |
@@ -1,6 +0,6 @@ | ||
export {HistoryEntry} from './outputs/types/history'; | ||
export {NormalizedRecord} from './outputs/types/record'; | ||
export {AgenceBioNormalizedOperator, AgenceBioNormalizedOperatorWithRecord} from './outputs/types/operator'; | ||
export {CartoBioFeature, CartoBioFeatureCollection, CartoBioFeatureProperties} from './outputs/types/features'; | ||
export {EtatProduction, CertificationState} from './outputs/record'; | ||
export {EventType} from './outputs/history'; | ||
export type {HistoryEntry} from './outputs/types/history.d.ts'; | ||
export type {NormalizedRecord, NormalizedRecordSummary} from './outputs/types/record.d.ts'; | ||
export type {AgenceBioNormalizedOperator, AgenceBioNormalizedOperatorWithRecord} from './outputs/types/operator.d.ts'; | ||
export type {CartoBioFeature, CartoBioFeatureCollection, CartoBioFeatureProperties} from './outputs/types/features.d.ts'; | ||
export type {EtatProduction, CertificationState} from './outputs/record.js'; | ||
export type {EventType} from './outputs/history.js'; |
284219
5230