diff --git a/README.md b/README.md index b2dd852aa..a9e315d49 100644 --- a/README.md +++ b/README.md @@ -120,17 +120,6 @@ par défaut : La doc de l'API est publiée directement par le service et est disponible à l'adresse : http://[serveur]:[port]/doc -Attention, l'API utilise une version de GDAL pour la lecture et la création des images du cache. Si une version de GDAL est déjà présente sur la machine, il peut y avoir des problèmes avec la variable d'environnement **PROJ_LIB** qui indique l'emplacement du dossier qui contient la définition des systèmes de coordonnées. Dans ce cas, l'API va signaler une erreur lors de l'application d'une retouche (erreur visible dans la console côté serveur et dans l'interface iTowns côté client). Si cela se produit, il faut supprimer la variable d'environnement **PROJ_LIB** avant de lancer l'API. -Sous MacOS ou Linux, cela peut être fait avec la commande : -``` -unset PROJ_LIB -``` -Sous Windows : -``` -SET PROJ_LIB= -``` - - ### Principe de fonctionnement Ce service propose: @@ -585,8 +574,10 @@ optional arguments: ## Raccourcis clavier Les raccourcis clavier disponibles dans l'interface sont : -- Sélectionner une OPI (*Select an OPI*) : **s** +- Sélectionner une OPI de réference (*Select ref OPI*) : **s** +- Sélectionner une OPI secondaire (*Select sec OPI*) : **w** - Démarrer polygone (*Start polygon*) : **p** +- Démarrer polygone pour saisie auto (*Start polygon auto*) : **t** - Annuler polygone (*Undo*) : **Ctrl + Z** - Refaire polygone (*Redo*) : **Ctrl + Y** - Masquer/Afficher l’Ortho mosaïquée : **m** diff --git a/db/db.js b/db/db.js index 77c7eb862..f7621fc59 100644 --- a/db/db.js +++ b/db/db.js @@ -1,5 +1,7 @@ const debug = require('debug')('db'); const format = require('pg-format'); +// const fs = require('fs'); +// const gjson = require('./geojson'); async function beginTransaction(pgClient) { debug('BEGIN'); @@ -180,7 +182,7 @@ async function getOPIFromColor(pgClient, idBranch, color) { } async function getOPIFromName(pgClient, idBranch, name) { - debug(` ~~getOpiId (name: ${name})`); + debug(` ~~getOPIFromName (name: ${name})`); const results = await pgClient.query( 'SELECT o.name, to_char(o.date,\'YYYY-mm-dd\') as date, o.time_ut, o.color, o.id, o.with_rgb, o.with_ir FROM opi o, branches b WHERE b.id_cache = o.id_cache AND b.id = $1 AND o.name=$2', [idBranch, name], @@ -192,19 +194,43 @@ async function getOPIFromName(pgClient, idBranch, name) { return results.rows[0]; } -async function insertPatch(pgClient, idBranch, geometry, opiId) { - debug(` ~~insertPatch (idBranch: ${idBranch})`); +// async function getOPIFromId(pgClient, idOpi) { +// debug(` ~~getOPIFromId (idOpi: ${idOpi})`); +// const results = await pgClient.query( +// 'SELECT name, to_char(date,\'YYYY-mm-dd\'), time_ut, color,' +// + 'with_rgb, with_ir FROM opi WHERE id=$1', +// [idOpi], +// ); +// if (results.rowCount !== 1) { +// throw new Error(`on a trouvé ${results.rowCount} opi pour le idOpi '${idOpi}'`); +// } +// return results.rows[0]; +// } + +async function getCacheCrsFromIdBranch(pgClient, idBranch) { + debug(` ~~getCacheCrs (idBranch: ${idBranch})`); + const results = await pgClient.query('SELECT crs FROM caches WHERE id=(SELECT id_cache FROM branches WHERE id=$1)', [idBranch]); + if (results.rowCount !== 1) { + throw new Error(`on a trouvé ${results.rowCount} crs pour le idBranch '${idBranch}'`); + } + return results.rows[0]; +} - const sql = format('INSERT INTO patches (geom, id_branch, id_opi) values (ST_GeomFromGeoJSON(%L), %s, %s) RETURNING id as id_patch, num', +async function insertPatch(pgClient, idBranch, geometry, opiRefId, opiSecId, isAuto) { + debug(` ~~insertPatch (idBranch: ${idBranch})`); + const sql = format('INSERT INTO patches (geom, id_branch, id_opi, id_opisec, is_auto) VALUES (ST_GeomFromGeoJSON(%L), %L) RETURNING id as id_patch, num', JSON.stringify(geometry), - idBranch, - opiId); + [idBranch, + opiRefId, + isAuto ? opiSecId : null, + isAuto]); debug(sql); const results = await pgClient.query(sql); if (results.rowCount !== 1) { throw new Error('failed to insert patch'); } + return results.rows[0]; } @@ -554,6 +580,7 @@ module.exports = { getUnactivePatches, getOPIFromColor, getOPIFromName, + getCrsFromIdBranch: getCacheCrsFromIdBranch, insertPatch, deactivatePatch, reactivatePatch, diff --git a/db/geojson.js b/db/geojson.js new file mode 100644 index 000000000..20913c1a9 --- /dev/null +++ b/db/geojson.js @@ -0,0 +1,45 @@ +const debug = require('debug')('gjson'); +const fs = require('fs'); + +async function writeGeojson(idBranch, idPatch, cachePath, geojson) { + debug(' ~~writeGeojson'); + // create dir if it does not exist + const dir = `${cachePath}/tmp_test_js`; + try { + return fs.mkdirSync(dir); + } catch (error) { + if (error.code !== 'EEXIST') debug(error); + } + + // write patch geojson + const filePath = `${dir}/patch_idBr${idBranch}_idP${idPatch}.geojson`; + + const geojsonAna = JSON.parse(JSON.stringify(geojson)); + + geojsonAna.name = `${idBranch}_${idPatch}`; + + const prop = geojson.features[0].properties; + if (prop.opiSec.name) { + geojsonAna.features[0].geometry.type = 'MultiLineString'; + } + geojsonAna.features[0].geometry.coordinates = [geojsonAna.features[0].geometry.coordinates]; + + geojsonAna.features[0].properties = { + opiName: prop.opiRef.name, + color: prop.opiRef.color, + opiName2: prop.opiSec.name, + colorSec: prop.opiSec.color, + }; + + try { + fs.writeFileSync(filePath, JSON.stringify(geojsonAna, null, 2), 'utf8'); + debug(` File '${filePath}' written`); + } catch (error) { + debug(error); + } + return filePath; +} + +module.exports = { + writeGeojson, +}; diff --git a/doc/BD_packo.drawio.png b/doc/BD_packo.drawio.png index 1c6da098f..a8028197a 100644 Binary files a/doc/BD_packo.drawio.png and b/doc/BD_packo.drawio.png differ diff --git a/doc/swagger.yml b/doc/swagger.yml index 4569e2ac1..af00b8b8c 100644 --- a/doc/swagger.yml +++ b/doc/swagger.yml @@ -22,6 +22,8 @@ tags: description: Récupération de fichiers - name: process description: Gestion des processus longs (donc asynchrones) + - name: ozcppexe + description: Lancement oz exe c++ paths: # VERSION '/version': @@ -822,3 +824,97 @@ paths: '200': description: OK + # CPP EXE + '/ozCppExe': + get: + tags: + - ozcppexe + summary: "Lancement exe retouche semi-auto du graphe" + description: "" + parameters: + - in: query + name: fstOpi + description: first opi path + required: true + schema: + type: array + uniqueItems: true + items: + type: string + example: [opi1_0.tif] + - in: query + name: secOpi + description: second opi path + required: true + schema: + type: array + uniqueItems: true + items: + type: string + example: [opi2_0.tif] + - in: query + name: patch + description: geojson patch path + required: true + schema: + type: string + example: patch.geojson + - in: query + name: graph + description: geotiff_graph_path + required: true + schema: + type: array + uniqueItems: true + items: + type: string + example: [graph_0.tif] + - in: query + name: weightDiffCost + description: weight of difference cost, between 0 and 1 + required: false + schema: + type: double + minimum: 0 + maximum: 1 + default: 0.95 + - in: query + name: weightTransition + description: weight of transition cost + required: false + schema: + type: double + minimum: 0 + default: 10.0 + - in: query + name: minCost + description: minimum cost + required: false + schema: + type: double + default: 0.0001 + - in: query + name: tension + description: tension + required: false + schema: + type: integer + default: 2 + - in: query + name: border + description: border in meters + required: false + schema: + type: integer + default: 20 + - in: query + name: outDir + description: output directory path + required: false + schema: + type: string + default: 'results' + responses: + '200': + description: OK + diff --git a/gdal_processing.js b/gdal_processing.js index ac5736844..74db5b266 100644 --- a/gdal_processing.js +++ b/gdal_processing.js @@ -6,6 +6,12 @@ const uuid = require('uuid'); const defaultImage = {}; +if (process.env.PROJ_LIB !== undefined) { + // const projPath = path.resolve(__dirname, './node_modules/gdal-async/deps/libproj/proj/data'); + const projPath = path.resolve(require.resolve('gdal-async'), '../../deps/libproj/proj/data'); + gdal.setPROJSearchPath(projPath); +} + /** * * @param {string} url - url de l'image @@ -153,7 +159,46 @@ async function getDefaultEncoded(formatGDAL, blocSize) { return gdal.vsimem.release(name); } -function processPatchAsync(patch, blocSize) { +async function getBands(ds) { + const size = await ds.rasterSizeAsync; + return Promise.all([ + ds.bands.getAsync(1).then( + (band) => band.pixels.readAsync(0, 0, size.x, size.y), + ), + ds.bands.getAsync(2).then( + (band) => band.pixels.readAsync(0, 0, size.x, size.y), + ), + ds.bands.getAsync(3).then( + (band) => band.pixels.readAsync(0, 0, size.x, size.y), + ), + ds.geoTransformAsync, + ds.srsAsync, + ]).then((res2) => ({ + bands: [res2[0], res2[1], res2[2]], + geoTransform: res2[3], + srs: res2[4], + size, + ds, + })); +} +async function getBand(ds) { + const size = await ds.rasterSizeAsync; + return Promise.all([ + ds.bands.getAsync(1).then( + (band) => band.pixels.readAsync(0, 0, size.x, size.y), + ), + ds.geoTransformAsync, + ds.srsAsync, + ]).then((res2) => ({ + bands: [res2[0]], + geoTransform: res2[1], + srs: res2[2], + size, + ds, + })); +} + +function processPatchAsync(patch, blocSize, isAuto) { return new Promise((res, reject) => { // On patch le graph const { mask } = patch; @@ -164,83 +209,61 @@ function processPatchAsync(patch, blocSize) { const urlGraph = patch.withOrig ? patch.urlGraphOrig : patch.urlGraph; const urlOrthoRgb = patch.withOrig ? patch.urlOrthoRgbOrig : patch.urlOrthoRgb; const urlOrthoIr = urlOrthoRgb.replace('.', 'i.'); - const { urlOpiRgb } = patch; - let urlOpiIr = urlOpiRgb; - const dname = path.dirname(urlOpiIr); - let fname = path.basename(urlOpiIr); - if (fname.includes('_ix') === false) { - fname = fname.includes('x') ? fname.replace('x', '_ix') : fname.replace('.', 'i.'); - urlOpiIr = path.join(dname, fname); - } - async function getBands(ds) { - const size = await ds.rasterSizeAsync; - return Promise.all([ - ds.bands.getAsync(1).then( - (band) => band.pixels.readAsync(0, 0, size.x, size.y), - ), - ds.bands.getAsync(2).then( - (band) => band.pixels.readAsync(0, 0, size.x, size.y), - ), - ds.bands.getAsync(3).then( - (band) => band.pixels.readAsync(0, 0, size.x, size.y), - ), - ds.geoTransformAsync, - ds.srsAsync, - ]).then((res2) => ({ - bands: [res2[0], res2[1], res2[2]], - geoTransform: res2[3], - srs: res2[4], - size, - ds, - })); + const { urlOpiRefRgb, urlOpiSecRgb } = patch; + let urlOpiRefIr = urlOpiRefRgb; + const dnameRef = path.dirname(urlOpiRefIr); + let fnameRef = path.basename(urlOpiRefIr); + if (fnameRef.includes('_ix') === false) { + fnameRef = fnameRef.includes('x') ? fnameRef.replace('x', '_ix') : fnameRef.replace('.', 'i.'); + urlOpiRefIr = path.join(dnameRef, fnameRef); } - async function getBand(ds) { - const size = await ds.rasterSizeAsync; - return Promise.all([ - ds.bands.getAsync(1).then( - (band) => band.pixels.readAsync(0, 0, size.x, size.y), - ), - ds.geoTransformAsync, - ds.srsAsync, - ]).then((res2) => ({ - bands: [res2[0]], - geoTransform: res2[1], - srs: res2[2], - size, - ds, - })); + let urlOpiSecIr = urlOpiSecRgb; + if (isAuto) { + const dnameSec = path.dirname(urlOpiSecIr); + let fnameSec = path.basename(urlOpiSecIr); + if (fnameSec.includes('_ix') === false) { + fnameSec = fnameSec.includes('x') ? fnameSec.replace('x', '_ix') : fnameSec.replace('.', 'i.'); + urlOpiSecIr = path.join(dnameSec, fnameSec); + } } + debug('chargement...'); Promise.all([ gdal.openAsync(urlGraph).then((ds) => getBands(ds)), - patch.withRgb ? gdal.openAsync(urlOpiRgb).then((ds) => getBands(ds)) : null, - patch.withIr ? gdal.openAsync(urlOpiIr).then((ds) => getBand(ds)) : null, + patch.withRgb ? gdal.openAsync(urlOpiRefRgb).then((ds) => getBands(ds)) : null, + patch.withIr ? gdal.openAsync(urlOpiRefIr).then((ds) => getBand(ds)) : null, patch.withRgb ? gdal.openAsync(urlOrthoRgb).then((ds) => getBands(ds)) : null, patch.withIr ? gdal.openAsync(urlOrthoIr).then((ds) => getBand(ds)) : null, + (isAuto && patch.withRgb) ? gdal.openAsync(urlOpiSecRgb).then((ds) => getBands(ds)) : null, + (isAuto && patch.withIr) ? gdal.openAsync(urlOpiSecIr).then((ds) => getBand(ds)) : null, ]).then(async (images) => { debug('... fin chargement'); debug('application du patch...'); + // TODO: opi sec const graph = images[0]; - const opiRgb = images[1]; - const opiIr = images[2]; + const opiRefRgb = images[1]; + const opiRefIr = images[2]; const orthoRgb = images[3]; const orthoIr = images[4]; + const opiSecRgb = (isAuto ? images[5] : 'none'); + const opiSecIr = (isAuto ? images[6] : 'none'); + graph.bands[0].forEach((_element, index) => { /* eslint-disable no-param-reassign */ if (mask.data[4 * index] > 0) { [graph.bands[0][index], graph.bands[1][index], - graph.bands[2][index]] = patch.color; + graph.bands[2][index]] = patch.colorRef; if (orthoRgb) { [orthoRgb.bands[0][index], orthoRgb.bands[1][index], orthoRgb.bands[2][index]] = [ - opiRgb.bands[0][index], - opiRgb.bands[1][index], - opiRgb.bands[2][index]]; + opiRefRgb.bands[0][index], + opiRefRgb.bands[1][index], + opiRefRgb.bands[2][index]]; } if (orthoIr) { - orthoIr.bands[0][index] = opiIr.bands[0][index]; + orthoIr.bands[0][index] = opiRefIr.bands[0][index]; } } /* eslint-enable no-param-reassign */ @@ -250,10 +273,10 @@ function processPatchAsync(patch, blocSize) { // on verifie que l orientation est bien interprété try { graph.srs.autoIdentifyEPSG(); - } catch (error) { - console.log('Erreur dans la gestion des SRS'); - console.log('Il faut probablement supprimer la variable PROJ_LIB de votre environnement'); - reject(new Error('PROJ_LIB Error')); + } catch (err) { + const error = new Error(`gdal-async -> srs.autoIdentifyEPSG: ${err.message}`); + console.log(error); + reject(error); } const graphMem = gdal.open('graph', 'w', 'MEM', graph.size.x, graph.size.y, 3); graphMem.geoTransform = graph.geoTransform; @@ -296,11 +319,19 @@ function processPatchAsync(patch, blocSize) { if (orthoIr) { orthoIr.ds.close(); } - if (opiRgb) { - opiRgb.ds.close(); + if (opiRefRgb) { + opiRefRgb.ds.close(); } - if (opiIr) { - opiIr.ds.close(); + if (opiRefIr) { + opiRefIr.ds.close(); + } + if (isAuto) { + if (opiSecRgb) { + opiSecRgb.ds.close(); + } + if (opiSecIr) { + opiSecIr.ds.close(); + } } Promise.all([ @@ -333,4 +364,4 @@ function processPatchAsync(patch, blocSize) { exports.getTileEncoded = getTileEncoded; exports.getColor = getColor; exports.getDefaultEncoded = getDefaultEncoded; -exports.processPatch = processPatchAsync; +exports.processPatchAsync = processPatchAsync; diff --git a/itowns/Api.js b/itowns/Api.js index f02418558..92abd48d8 100644 --- a/itowns/Api.js +++ b/itowns/Api.js @@ -173,7 +173,7 @@ class API { if (res.status === 200) { resolve(); } else { - res.json().then((json) => reject(json.msg)); + res.json().then((json) => reject(json)); } }); }); diff --git a/itowns/Editing.js b/itowns/Editing.js index b8d3a65d9..6c8cbc954 100644 --- a/itowns/Editing.js +++ b/itowns/Editing.js @@ -5,13 +5,25 @@ import * as THREE from 'three'; const status = { RAS: 0, SELECT: 1, - POLYGON: 2, + SAISIE: 2, ENDING: 3, WAITING: 4, WRITING: 5, ADDREMARK: 6, }; +export const saisie = { + LineString: 0, + Polygon: 1, +}; + +const actvBtnColor = '#FF0000'; + +const saisieColor = { + LineString: '#FFFF00', + Polygon: '#FF0000', +}; + function getAllCheckboxes(id, className) { const allCheckboxes = []; let propEls; @@ -35,9 +47,17 @@ class Editing { this.api = this.viewer.api; this.menu = menu; - this.opiName = 'none'; - this.opiDate = ''; - this.opiTime = ''; + this.currentOpi = 0; + + this.opi1Name = 'none'; + this.opi1Date = ''; + this.opi1Time = ''; + this.opi1Color = ''; + + this.opi2Name = 'none'; + this.opi2Date = ''; + this.opi2Time = ''; + this.opi2Color = ''; this.coord = `${this.viewer.xcenter.toFixed(2)},${this.viewer.ycenter.toFixed(2)}`; @@ -65,15 +85,14 @@ class Editing { this.coord = `${this.mousePosition.x.toFixed(2)},${this.mousePosition.y.toFixed(2)}`; if (this.currentPolygon == null) return; - if (this.currentStatus === status.POLYGON && this.nbVertices > 0) { + if (this.currentStatus === status.SAISIE && this.nbVertices > 0) { const vertices = this.currentPolygon.geometry.attributes.position; const newPoint = new THREE.Vector3(); newPoint.subVectors(this.mousePosition, this.currentPolygon.position); vertices.set(newPoint.toArray(), 3 * this.nbVertices); vertices.copyAt(this.nbVertices + 1, vertices, 0); vertices.needsUpdate = true; - - this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + 2); + this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + this.saisie.type + 1); this.currentPolygon.geometry.computeBoundingSphere(); this.view.notifyChange(this.currentPolygon); } @@ -88,9 +107,23 @@ class Editing { } this.viewer.message = 'calcul en cours'; this.view.controls.setCursor('default', 'wait'); + + const saisieTypeStr = Object.keys(saisie)[this.saisie.type]; + console.log(` -> patch: ${saisieTypeStr}, currentStatus: ${this.currentStatus}`); this.currentStatus = status.WAITING; const positions = this.currentPolygon.geometry.attributes.position.array; + const coordinates = []; + for (let i = 0; i <= this.nbVertices; i += 1) { + coordinates.push( + [ + positions[3 * i] + this.currentPolygon.position.x, + positions[3 * i + 1] + this.currentPolygon.position.y, + ], + ); + } + + // On créer le geoJSON const geojson = { type: 'FeatureCollection', name: 'annotation', @@ -98,28 +131,26 @@ class Editing { features: [ { type: 'Feature', - properties: { color: this.color, opiName: this.opiName }, + properties: { + opiRef: { + name: this.opi1Name, + color: this.opi1Color, + }, + ...(!this.saisie.type && { + opiSec: { + name: this.opi2Name, + color: this.opi2Color, + }, + }), + }, geometry: { - type: 'Polygon', - coordinates: [[]], + type: saisieTypeStr, + coordinates: this.saisie.type ? [coordinates] : coordinates.slice(0, -1), }, }, ], }; - for (let i = 0; i <= this.nbVertices; i += 1) { - geojson.features[0].geometry.coordinates[0].push( - [ - positions[3 * i] + this.currentPolygon.position.x, - positions[3 * i + 1] + this.currentPolygon.position.y, - ], - ); - } - - // this.view.scene.remove(this.currentPolygon); - // this.currentStatus = status.WAITING; - // this.view.controls.setCursor('default', 'wait'); - // this.viewer.message = 'calcul en cours'; // On post le geojson sur l'API this.api.postPatch(this.branch.active.id, JSON.stringify(geojson)) @@ -129,7 +160,7 @@ class Editing { }) .catch((error) => { console.log(error); - this.viewer.message = error.message; + this.viewer.message = 'Erreur post patch'; this.viewer.view.dispatchEvent({ type: 'error', error, @@ -139,7 +170,7 @@ class Editing { this.resetCurrentPolygon(); this.view.controls.setCursor('default', 'auto'); this.currentStatus = status.RAS; - this.menu.getController('polygon').setBackgroundColorTo(''); + this.menu.getController(saisieTypeStr).setBackgroundColorTo(''); }); } @@ -175,10 +206,13 @@ class Editing { if (e.ctrlKey && (e.key === 'z')) this.undo(); // redo CTRL+y if (e.ctrlKey && (e.key === 'y')) this.redo(); - // select Opi - if (e.key === 's') this.select(); + // select Opi 1 + if (e.key === 's') this.select(1); + // select Opi 2 + if (e.key === 'w') this.select(2); // start polygon - if ((e.key === 'p') && (this.branch.active.name !== 'orig')) this.polygon(); + if ((e.key === 'p') && (this.branch.active.name !== 'orig')) this.saisie('Polygon'); + if ((e.key === 't') && (this.branch.active.name !== 'orig')) this.saisie('LineString'); // change visibility on ColorLayers Object.keys(this.viewer.shortCuts.visibleFolder).forEach((key) => { if (e.key === this.viewer.shortCuts.visibleFolder[key]) { @@ -187,7 +221,7 @@ class Editing { } }); // change visibility on ExtraLayers - if (e.key === 'v') { + if (e.key === this.viewer.shortCuts.layerFolders.extraLayers) { console.log('Change Extra Layers visibility'); getAllCheckboxes('extraLayers', 'visibcbx').forEach((c) => (c.click())); } @@ -234,13 +268,27 @@ class Editing { } // L'utilisateur demande à déselectionner l'OPI - if (this.opiName !== 'none' && (e.key === 'Escape')) { - this.opiName = 'none'; - this.menu.getController('opiName').setBackgroundColorTo(''); - this.view.dispatchEvent({ - type: 'opi-selected', - name: 'none', - }); + if (e.key === 'Escape') { + if (this.opi2Name !== 'none') { + this.opi2Name = 'none'; + this.menu.setOpi2DataCtr(this.opi2Name); + this.menu.getController('opi2Name').setBackgroundColorTo(''); + this.menu.getController('select2').setBackgroundColorTo(''); + this.view.changeOpi(this.opi1Name); + this.view.dispatchEvent({ + type: 'opi-selected', + name: this.opi1Name, + id: 1, + }); + } else if (this.opi1Name !== 'none') { + this.opi1Name = 'none'; + this.menu.getController('opi1Name').setBackgroundColorTo(''); + this.view.dispatchEvent({ + type: 'opi-selected', + name: 'none', + id: 1, + }); + } } else if (this.branch.alert.layerName !== '-' && this.branch.alert.nbTotal > 0) { if (e.key === 'ArrowLeft') { this.branch.alert.selectPrevious({ centerOnFeature: true }); @@ -256,10 +304,10 @@ class Editing { } if (e.key === 'Escape') { if (this.currentStatus === status.SELECT) { - this.menu.getController('select').setBackgroundColorTo(''); + this.menu.getController(`select${this.currentOpi}`).setBackgroundColorTo(''); } - if (this.currentStatus === status.POLYGON) { - this.menu.getController('polygon').setBackgroundColorTo(''); + if (this.currentStatus === status.SAISIE) { + this.menu.getController(Object.keys(saisie)[this.saisie.type]).setBackgroundColorTo(''); this.resetCurrentPolygon(); } if (this.currentStatus === status.ADDREMARK) { @@ -269,7 +317,7 @@ class Editing { this.view.controls.setCursor('default', 'auto'); this.currentStatus = status.RAS; } - if (this.currentStatus === status.POLYGON) { + if (this.currentStatus === status.SAISIE) { if (e.key === 'Shift') { if (this.currentPolygon) { if (this.branch.active.name === 'orig') { @@ -287,7 +335,7 @@ class Editing { vertices.copyAt(this.nbVertices, vertices, 0); vertices.needsUpdate = true; - this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + 1); + this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + this.saisie.type); this.currentPolygon.geometry.computeBoundingSphere(); this.view.notifyChange(this.currentPolygon); } @@ -299,7 +347,7 @@ class Editing { vertices.copyAt(this.nbVertices, vertices, 0); vertices.needsUpdate = true; - this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + 1); + this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + this.saisie.type); this.currentPolygon.geometry.computeBoundingSphere(); this.view.notifyChange(this.currentPolygon); this.nbVertices -= 1; @@ -312,7 +360,7 @@ class Editing { if (this.currentStatus === status.WAITING) return; console.log(e.key, ' up'); if (e.key === 'Shift') { - if (this.currentStatus === status.ENDING || this.currentStatus === status.POLYGON) { + if (this.currentStatus === status.ENDING || this.currentStatus === status.SAISIE) { this.viewer.message = 'Maj pour terminer'; if (this.currentPolygon && (this.nbVertices > 0)) { // on remet le dernier sommet sur la position de la souris @@ -323,12 +371,12 @@ class Editing { vertices.set(newPoint.toArray(), 3 * this.nbVertices); vertices.needsUpdate = true; - this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + 2); + this.currentPolygon.geometry.setDrawRange(0, this.nbVertices + this.saisie.type + 1); this.currentPolygon.geometry.computeBoundingSphere(); this.view.notifyChange(this.currentPolygon); this.view.controls.setCursor('default', 'crosshair'); } - this.currentStatus = status.POLYGON; + this.currentStatus = status.SAISIE; } } } @@ -348,24 +396,34 @@ class Editing { this.viewer.message = 'calcul en cours'; this.view.controls.setCursor('default', 'wait'); this.currentStatus = status.WAITING; - // on selectionne l'Opi + // on selectionne une Opi this.api.getGraph(this.branch.active.id, mousePosition) .then((opi) => { + if (this.opi1Name !== 'none' && ([this.opi1Name, this.opi2Name].includes(opi.opiName))) { + this.viewer.message = 'Même opi'; + this.view.controls.setCursor('default', 'crosshair'); + this.currentStatus = status.SELECT; + return; + } this.viewer.message = ''; this.view.controls.setCursor('default', 'auto'); this.currentStatus = status.RAS; - this.menu.getController('select').setBackgroundColorTo(''); + this.menu.getController(`select${this.currentOpi}`).setBackgroundColorTo(''); + + const opiName = `opi${this.currentOpi}Name`; + this[opiName] = opi.opiName; + this[`opi${this.currentOpi}Date`] = opi.date; + this[`opi${this.currentOpi}Time`] = opi.time; + this[`opi${this.currentOpi}Color`] = opi.color; - this.opiName = opi.opiName; - this.opiDate = opi.date; - this.opiTime = opi.time; - this.color = opi.color; - this.menu.getController('opiName').setBackgroundColorTo(`rgb(${this.color[0]},${this.color[1]},${this.color[2]})`); + this.menu.getController(opiName) + .setBackgroundColorTo(`rgb(${opi.color[0]},${opi.color[1]},${opi.color[2]})`); // On modifie la source de la couche OPI - this.view.changeOpi(this.opiName); + this.view.changeOpi(this[opiName]); this.view.dispatchEvent({ type: 'opi-selected', - name: this.opiName, + name: this[opiName], + id: this.currentOpi, }); }) .catch((error) => { @@ -374,10 +432,10 @@ class Editing { this.view.controls.setCursor('default', 'crosshair'); this.currentStatus = status.SELECT; } else { - this.viewer.message = 'PB de mise à jour de la BdD'; + this.viewer.message = `Opi ${this.currentOpi}: PB de BdD`; this.view.controls.setCursor('default', 'auto'); this.currentStatus = status.RAS; - this.menu.getController('select').setBackgroundColorTo(''); + this.menu.getController(`select${this.currentOpi}`).setBackgroundColorTo(''); this.view.dispatchEvent({ type: 'error', error, @@ -386,7 +444,7 @@ class Editing { }); break; } - case status.POLYGON: { + case status.SAISIE: { if (this.branch.active.name === 'orig') { this.viewer.message = 'Changer de branche pour continuer'; } else if (this.viewer.dezoom > this.viewer.maxGraphDezoom) { @@ -421,27 +479,30 @@ class Editing { break; } case status.ENDING: - // on termine la ployline ou polygon + // on termine la polyline (LineString) ou polygon this.update(); break; default: } } - select() { + select(id) { + if (this.currentStatus === status.SELECT && this.currentOpi !== id) { + this.viewer.message = `Opi ${this.currentOpi} non encore choisie`; + } if (this.currentStatus !== status.RAS) return; - // this.cancelcurrentPolygon(); console.log('"select": En attente de sélection'); - this.viewer.message = 'choisir une Opi'; + this.viewer.message = `choisir l'Opi ${id}`; this.view.controls.setCursor('default', 'crosshair'); this.currentStatus = status.SELECT; - this.menu.getController('select').setBackgroundColorTo('#BB0000'); + this.currentOpi = id; + this.menu.getController(`select${id}`).setBackgroundColorTo(actvBtnColor); } - polygon() { + saisie(type) { if (this.currentStatus === status.WAITING) return; - if (this.opiName === 'none') { - this.viewer.message = (this.currentStatus === status.SELECT) ? 'Opi pas encore choisie' : "pas d'Opi sélectionnée"; + if (this.currentStatus === status.SELECT) { + this.viewer.message = `Sélection Opi ${this.currentOpi} en cours`; return; } if (this.currentPolygon) { @@ -449,11 +510,30 @@ class Editing { // saisie deja en cours return; } - console.log("saisie d'un polygon"); - this.viewer.message = "saisie d'un polygon"; + + if (saisie[type] === saisie.Polygon) { + if (this.opi1Name === 'none') { + this.viewer.message = 'pas d\'Opi sélectionnée'; + return; + } + if (this.opi2Name !== 'none') { + this.viewer.message = '2 Opi sélectionnées !'; + return; + } + } else if (saisie[type] === saisie.LineString) { + if (this.opi1Name === 'none' || this.opi2Name === 'none') { + this.viewer.message = 'Sélectionnez 2 Opi'; + return; + } + } + + this.saisie.type = saisie[type]; + this.saisie.color = saisieColor[type]; + console.log(`Saisie d'un.e ${type}`); + this.viewer.message = `Saisie d'un.e ${type}`; this.view.controls.setCursor('default', 'crosshair'); - this.menu.getController('select').setBackgroundColorTo(''); - this.menu.getController('polygon').setBackgroundColorTo('#BB0000'); + this.menu.getController(`select${this.currentOpi}`).setBackgroundColorTo(''); + this.menu.getController(type).setBackgroundColorTo(this.saisie.color); const MAX_POINTS = 500; const geometry = new THREE.BufferGeometry(); @@ -461,7 +541,7 @@ class Editing { geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.setDrawRange(0, 1); const material = new THREE.LineBasicMaterial({ - color: 0xFF0000, + color: this.saisie.color, depthTest: false, depthWrite: false, }); @@ -471,7 +551,7 @@ class Editing { this.currentPolygon.maxMarkers = -1; this.view.scene.add(this.currentPolygon); this.view.notifyChange(this.currentPolygon); - this.currentStatus = status.POLYGON; + this.currentStatus = status.SAISIE; this.nbVertices = 0; } @@ -564,7 +644,7 @@ class Editing { this.viewer.message = "saisie d'une remarque"; this.view.controls.setCursor('default', 'crosshair'); this.currentStatus = status.ADDREMARK; - this.menu.getController('addRemark').setBackgroundColorTo('#BB0000'); + this.menu.getController('addRemark').setBackgroundColorTo(actvBtnColor); } postRemark(mousePosition, remark) { diff --git a/itowns/Menu.js b/itowns/Menu.js index bcb898a21..680dd6513 100644 --- a/itowns/Menu.js +++ b/itowns/Menu.js @@ -35,7 +35,12 @@ dat.controllers.Controller.prototype.selectIndex = function selectIndex(newIndex this.__select.options.selectedIndex = newIndex; }; dat.controllers.Controller.prototype.setBackgroundColorTo = function _(newColor) { - this.__li.style.backgroundColor = newColor; + console.log(this.property, /^opi\dName$/g.test(this.property)); + if (/^opi\dName$/g.test(this.property)) { + this.__li.children[0].children[0].style.backgroundColor = newColor; + } else { + this.__li.style.backgroundColor = newColor; + } }; /* eslint-enable no-underscore-dangle */ @@ -50,9 +55,15 @@ class Menu extends dat.GUI { menuDiv.appendChild(this.domElement); - this.colorGui = this.addFolder('Color Layers'); + const colorLayersName = `Color Layers ${(this.shortCuts.layerFolders.ColorLayers + ? ` [${this.shortCuts.layerFolders.ColorLayers}]` + : '')}`; + const extraLayersName = `Extra Layers ${(this.shortCuts.layerFolders.extraLayers + ? ` [${this.shortCuts.layerFolders.extraLayers}]` + : '')}`; + this.colorGui = this.addFolder(colorLayersName); this.colorGui.open(); - this.vectorGui = this.addFolder('Extra Layers [v]'); + this.vectorGui = this.addFolder(extraLayersName); this.vectorGui.domElement.id = 'extraLayers'; this.vectorGui.open(); @@ -84,7 +95,7 @@ class Menu extends dat.GUI { } setPatchCtr(branchName) { - this[branchName !== 'orig' ? 'show' : 'hide'](['polygon', 'undo', 'redo']); + this[branchName !== 'orig' ? 'show' : 'hide'](['Polygon', 'LineString', 'undo', 'redo']); if (process.env.NODE_ENV === 'development') this[branchName !== 'orig' ? 'show' : 'hide']('clear'); } @@ -93,8 +104,12 @@ class Menu extends dat.GUI { this[layerName === 'Remarques' ? 'show' : 'hide'](['delRemark']); } - setOpiCtr(opiName) { - this[opiName === 'none' ? 'hide' : 'show'](['opiName', 'opiDate', 'opiTime']); + setOpi1DataCtr(opiName) { + this[opiName === 'none' ? 'hide' : 'show'](['opi1Name', 'opi1Date', 'opi1Time', 'select2']); + } + + setOpi2DataCtr(opiName) { + this[opiName === 'none' ? 'hide' : 'show'](['opi2Name', 'opi2Date', 'opi2Time']); } refreshDropBox(dropBoxName, listOfValues, diff --git a/itowns/Viewer.js b/itowns/Viewer.js index a22c6c19f..0d5aac8b4 100644 --- a/itowns/Viewer.js +++ b/itowns/Viewer.js @@ -145,6 +145,7 @@ class Viewer { this.oldStyle = {}; this.shortCuts = { + layerFolders: { ColorLayers: '', extraLayers: 'v' }, visibleFolder: { Ortho: 'm', Opi: 'o', Contour: 'g' }, styleFolder: { Ortho: 'i', Opi: 'i' }, }; diff --git a/itowns/index.js b/itowns/index.js index 23356f879..1d6e2111b 100644 --- a/itowns/index.js +++ b/itowns/index.js @@ -2,7 +2,7 @@ /* global setupLoadingScreen */ import * as itowns from 'itowns'; import Viewer from './Viewer'; -import Editing from './Editing'; +import Editing, { saisie } from './Editing'; import Alert from './Alert'; import Branch from './Branch'; import Menu from './Menu'; @@ -167,24 +167,44 @@ async function main() { .name('Add new branch'); // Selection OPI - menu.add(editing, 'select').name('Select an OPI [s]'); - menu.add(editing, 'opiName') - .name('OPI selected').listen() + menu.add({ select1: editing.select.bind(editing, 1) }, 'select1') + .name('Select an OPI [s]');// TODO linked name with shortcut + + menu.add(editing, 'opi1Name') + .name('OPI selected') + .listen() .onChange((name) => { console.log('opi selected: ', name); }) .domElement.parentElement.style.pointerEvents = 'none'; - menu.add(editing, 'opiDate') - .name('Date').listen() + menu.add(editing, 'opi1Date') + .name('➢ Date').listen() + .domElement.parentElement.style.pointerEvents = 'none'; + menu.add(editing, 'opi1Time') + .name('➢ Time').listen() .domElement.parentElement.style.pointerEvents = 'none'; - menu.add(editing, 'opiTime') - .name('Time').listen() + + menu.add({ select2: editing.select.bind(editing, 2) }, 'select2') + .name('Select a 2nd OPI [w]'); + menu.add(editing, 'opi2Name') + .name('OPI selected') + .listen() + .onChange((name) => { + console.log('opi selected: ', name); + }) + .domElement.parentElement.style.pointerEvents = 'none'; + + menu.add(editing, 'opi2Date') + .name('➢ Date').listen() + .domElement.parentElement.style.pointerEvents = 'none'; + menu.add(editing, 'opi2Time') + .name('➢ Time').listen() .domElement.parentElement.style.pointerEvents = 'none'; // Coord menu.add(editing, 'coord') - .name('Coordinates').listen() - .onChange(() => { + .name('Coordinates') + .listen().onChange(() => { if (!checkCoordString(editing.coord)) { viewer.message = 'Coordonnees non valides'; } else { @@ -204,7 +224,12 @@ async function main() { }); // Saisie - menu.add(editing, 'polygon').name('Start polygon [p]'); + const polygonStr = Object.keys(saisie)[saisie.Polygon]; + menu.add({ [polygonStr]: editing.saisie.bind(editing, polygonStr) }, polygonStr) + .name('Polygon patch [p]'); + const lineStringStr = Object.keys(saisie)[saisie.LineString]; + menu.add({ [lineStringStr]: editing.saisie.bind(editing, lineStringStr) }, lineStringStr) + .name('Semi-auto patch [t]');// TODO link to shortcut dictionnary menu.add(editing, 'undo').name('undo [CTRL+Z]'); menu.add(editing, 'redo').name('redo [CTRL+Y]'); menu.add(editing, 'clear') @@ -263,7 +288,8 @@ async function main() { // visibility of controllers menu.setPatchCtr(branch.active.name);// branch.active.name = 'orig' menu.setAlertCtr(alert.layerName);// alert.layerName = '-' - menu.setOpiCtr(editing.opiName);// editing.opiName = 'none' + menu.setOpi1DataCtr(editing.opi1Name);// editing.opi1Name = 'none' + menu.setOpi2DataCtr(editing.opi2Name);// editing.opi2Name = 'none' viewerDiv.focus(); @@ -317,13 +343,20 @@ async function main() { view.addEventListener('opi-selected', (newOpi) => { console.log(`-> Opi '${newOpi.name}' selected.`); + const cboxVisi = document.getElementById('Opi').children[0]; if (newOpi.name === 'none') { view.getLayerById('Opi').visible = false; view.notifyChange(view.getLayerById('Opi'), true); + if (cboxVisi.checked) { + cboxVisi.click(); + } } else { viewer.refresh('Opi'); + if (!cboxVisi.checked) { + cboxVisi.click(); + } } - menu.setOpiCtr(newOpi.name); + menu[`setOpi${newOpi.id}DataCtr`](newOpi.name); }); view.addEventListener('branch-created', (newBranch) => { @@ -397,9 +430,9 @@ async function main() { }); view.addEventListener('error', (ev) => { + console.log(ev.error instanceof Array ? ev.error.map((err) => err.status) : ev.error.msg); // eslint-disable-next-line no-alert - console.log(ev.error instanceof Array ? ev.error.map((error) => error.message).join('') : ev.error.message); - window.alert(ev.error instanceof Array ? ev.error.join('') : ev.error); + window.alert(ev.error instanceof Array ? ev.error.map((err) => err.status).join('\n') : ev.error.msg); }); viewerDiv.addEventListener('mousemove', (ev) => { diff --git a/middlewares/branch.js b/middlewares/branch.js index 033b06ea5..1c86081ea 100644 --- a/middlewares/branch.js +++ b/middlewares/branch.js @@ -189,7 +189,9 @@ async function rebase(req, res, next) { const patchInserted = await db.insertPatch(req.client, idNewBranch, feature.geometry, - feature.properties.id_opi); + feature.properties.id_opi, + feature.properties.id_opisec, + feature.properties.id_type); const idNewPatch = patchInserted.id_patch; const slabs = []; diff --git a/middlewares/patch.js b/middlewares/patch.js index 590b6ec7d..4d26e7b9f 100644 --- a/middlewares/patch.js +++ b/middlewares/patch.js @@ -1,16 +1,22 @@ const debug = require('debug')('patch'); +const debugOzcpp = require('debug')('patch:Ozcpp'); const fs = require('fs'); const canvas = require('canvas'); const turf = require('@turf/turf'); const path = require('path'); const { matchedData } = require('express-validator'); +const { execFile } = require('child_process'); const cog = require('../cog_path'); const gdalProcessing = require('../gdal_processing'); const db = require('../db/db'); +const gjson = require('../db/geojson'); -function getCOGs(feature, overviews) { +const ozExe = process.env.OZEXE; +let dirTmp = ''; + +function getCOGs(coordinates, overviews, borderMeters = 0) { const BBox = {}; - feature.geometry.coordinates[0].forEach((point) => { + coordinates.forEach((point) => { if ('xmin' in BBox) { BBox.xmin = Math.min(BBox.xmin, point[0]); BBox.xmax = Math.max(BBox.xmax, point[0]); @@ -21,6 +27,11 @@ function getCOGs(feature, overviews) { [BBox.xmax, BBox.ymax] = point; } }); + BBox.xmin -= borderMeters; + BBox.ymin -= borderMeters; + BBox.xmax += borderMeters; + BBox.ymax += borderMeters; + debug('~BBox: Done'); const cogs = []; @@ -51,124 +62,150 @@ function rename(url, urlOrig) { // Preparation des masques function createPatch(slab, feature, - color, - name, + colorRef, + nameRef, + colorSec, + nameSec, withRgb, withIr, overviews, dirCache, - idBranch) { - debug('~~createPatch : ', slab, feature, color, name, withRgb, withIr); - const xOrigin = overviews.crs.boundingBox.xmin; - const yOrigin = overviews.crs.boundingBox.ymax; - const slabWidth = overviews.tileSize.width * overviews.slabSize.width; - const slabHeight = overviews.tileSize.height * overviews.slabSize.height; + idBranch, + isAuto) { + debug('~~createPatch : ', slab, feature, colorRef, nameRef, colorSec, nameSec, withRgb, withIr, isAuto); - const resolution = overviews.resolution * 2 ** (overviews.level.max - slab.z); - const inputRings = []; - for (let n = 0; n < feature.geometry.coordinates.length; n += 1) { - const coordinates = feature.geometry.coordinates[n]; - const ring = []; - for (let i = 0; i < coordinates.length; i += 1) { - const point = coordinates[i]; - const x = Math.round((point[0] - xOrigin - slab.x * slabWidth * resolution) + const patchData = { + slab, colorRef, colorSec, withRgb, withIr, + }; + + // polygone + if (!isAuto) { + const xOrigin = overviews.crs.boundingBox.xmin; + const yOrigin = overviews.crs.boundingBox.ymax; + const slabWidth = overviews.tileSize.width * overviews.slabSize.width; + const slabHeight = overviews.tileSize.height * overviews.slabSize.height; + + const resolution = overviews.resolution * 2 ** (overviews.level.max - slab.z); + const inputRings = []; + for (let n = 0; n < feature.geometry.coordinates.length; n += 1) { + const coordinates = feature.geometry.coordinates[n]; + const ring = []; + for (let i = 0; i < coordinates.length; i += 1) { + const point = coordinates[i]; + const x = Math.round((point[0] - xOrigin - slab.x * slabWidth * resolution) / resolution); - const y = Math.round((yOrigin - point[1] - slab.y * slabHeight * resolution) + const y = Math.round((yOrigin - point[1] - slab.y * slabHeight * resolution) / resolution); - ring.push([x, y]); + ring.push([x, y]); + } + inputRings.push(ring); } - inputRings.push(ring); - } - const bbox = [0, 0, slabWidth, slabHeight]; - const poly = turf.polygon(inputRings); - const clipped = turf.bboxClip(poly, bbox); - const rings = clipped.geometry.coordinates; + const bbox = [0, 0, slabWidth, slabHeight]; + const poly = turf.polygon(inputRings); + const clipped = turf.bboxClip(poly, bbox); + const rings = clipped.geometry.coordinates; - if (rings.length === 0) { - debug('masque vide, on passe a la suite : ', slab); - return null; - } + if (rings.length === 0) { + debug('masque vide, on passe a la suite : ', slab); + return null; + } - // La BBox et le polygone s'intersectent - debug('on calcule un masque : ', slab); - const mask = canvas.createCanvas(slabWidth, slabHeight); - const ctx = mask.getContext('2d'); - ctx.fillStyle = '#FFFFFF'; - for (let n = 0; n < rings.length; n += 1) { - const ring = rings[n]; - // console.log(ring); - ctx.beginPath(); - ctx.moveTo(ring[0][0], ring[0][1]); - for (let i = 1; i < ring.length; i += 1) { - ctx.lineTo(ring[i][0], ring[i][1]); + // La BBox et le polygone s'intersectent + debug('on calcule un masque : ', slab); + const mask = canvas.createCanvas(slabWidth, slabHeight); + const ctx = mask.getContext('2d'); + ctx.fillStyle = '#FFFFFF'; + for (let n = 0; n < rings.length; n += 1) { + const ring = rings[n]; + // console.log(ring); + ctx.beginPath(); + ctx.moveTo(ring[0][0], ring[0][1]); + for (let i = 1; i < ring.length; i += 1) { + ctx.lineTo(ring[i][0], ring[i][1]); + } + ctx.closePath(); + ctx.fill(); } - ctx.closePath(); - ctx.fill(); - } - mask.data = mask.toBuffer('raw'); + mask.data = mask.toBuffer('raw'); + patchData.mask = mask; + // end polygone + } - const P = { - slab, mask, color, withRgb, withIr, - }; - P.cogPath = cog.getSlabPath( - P.slab.x, - P.slab.y, - P.slab.z, + const cogPath = cog.getSlabPath( + slab.x, + slab.y, + slab.z, overviews.pathDepth, ); - const nameRgb = withRgb ? name : name.replace('_ix', 'x'); - const nameIr = withRgb ? name.replace('x', '_ix') : name; - P.urlGraph = path.join(dirCache, 'graph', P.cogPath.dirPath, - `${idBranch}_${P.cogPath.filename}.tif`); - P.urlOrthoRgb = path.join(dirCache, 'ortho', P.cogPath.dirPath, - `${idBranch}_${P.cogPath.filename}.tif`); - P.urlOrthoIr = path.join(dirCache, 'ortho', P.cogPath.dirPath, - `${idBranch}_${P.cogPath.filename}i.tif`); - P.urlOpiRgb = path.join(dirCache, 'opi', P.cogPath.dirPath, - `${P.cogPath.filename}_${nameRgb}.tif`); - P.urlOpiIr = path.join(dirCache, 'opi', P.cogPath.dirPath, - `${P.cogPath.filename}_${nameIr}.tif`); - P.urlGraphOrig = path.join(dirCache, 'graph', P.cogPath.dirPath, - `${P.cogPath.filename}.tif`); - P.urlOrthoRgbOrig = path.join(dirCache, 'ortho', P.cogPath.dirPath, - `${P.cogPath.filename}.tif`); - P.urlOrthoIrOrig = path.join(dirCache, 'ortho', P.cogPath.dirPath, - `${P.cogPath.filename}i.tif`); - P.withOrig = false; + + patchData.cogPath = cogPath; + + const nameRefRgb = withRgb ? nameRef : nameRef.replace('_ix', 'x'); + const nameRefIr = withRgb ? nameRef.replace('x', '_ix') : nameRef; + patchData.urlGraph = path.join(dirCache, 'graph', cogPath.dirPath, + `${idBranch}_${cogPath.filename}.tif`); + patchData.urlOrthoRgb = path.join(dirCache, 'ortho', cogPath.dirPath, + `${idBranch}_${cogPath.filename}.tif`); + patchData.urlOrthoIr = path.join(dirCache, 'ortho', cogPath.dirPath, + `${idBranch}_${cogPath.filename}i.tif`); + patchData.urlOpiRefRgb = path.join(dirCache, 'opi', cogPath.dirPath, + `${cogPath.filename}_${nameRefRgb}.tif`); + patchData.urlOpiRefIr = path.join(dirCache, 'opi', cogPath.dirPath, + `${cogPath.filename}_${nameRefIr}.tif`); + if (isAuto) { + const nameSecRgb = withRgb ? nameSec : nameSec.replace('_ix', 'x'); + const nameSecIr = withRgb ? nameSec.replace('x', '_ix') : nameSec; + patchData.urlOpiSecRgb = path.join(dirCache, 'opi', cogPath.dirPath, + `${cogPath.filename}_${nameSecRgb}.tif`); + patchData.urlOpiSecIr = path.join(dirCache, 'opi', cogPath.dirPath, + `${cogPath.filename}_${nameSecIr}.tif`); + } + patchData.urlGraphOrig = path.join(dirCache, 'graph', cogPath.dirPath, + `${cogPath.filename}.tif`); + patchData.urlOrthoRgbOrig = path.join(dirCache, 'ortho', cogPath.dirPath, + `${cogPath.filename}.tif`); + patchData.urlOrthoIrOrig = path.join(dirCache, 'ortho', cogPath.dirPath, + `${cogPath.filename}i.tif`); + patchData.withOrig = false; + + // Give acces to file. A REFACTO const promises = []; - promises.push(fs.promises.access(P.urlGraph, fs.constants.F_OK).catch( + promises.push(fs.promises.access(patchData.urlGraph, fs.constants.F_OK).catch( () => { - fs.promises.access(P.urlGraphOrig, fs.constants.F_OK) + fs.promises.access(patchData.urlGraphOrig, fs.constants.F_OK) // cas ou le patch sort du cache --> géré avec Opi .catch(() => {}); - P.withOrig = true; + patchData.withOrig = true; }, )); - if (P.withRgb) { - promises.push(fs.promises.access(P.urlOrthoRgb, fs.constants.F_OK).catch( + if (patchData.withRgb) { + promises.push(fs.promises.access(patchData.urlOrthoRgb, fs.constants.F_OK).catch( () => { - fs.promises.access(P.urlOrthoRgbOrig, fs.constants.F_OK) + fs.promises.access(patchData.urlOrthoRgbOrig, fs.constants.F_OK) // cas ou le patch sort du cache --> géré avec Opi .catch(() => {}); - P.withOrig = true; + patchData.withOrig = true; }, )); - promises.push(fs.promises.access(P.urlOpiRgb, fs.constants.F_OK)); + promises.push(fs.promises.access(patchData.urlOpiRefRgb, fs.constants.F_OK)); + if (isAuto) promises.push(fs.promises.access(patchData.urlOpiSecRgb, fs.constants.F_OK)); } - if (P.withIr) { - promises.push(fs.promises.access(P.urlOrthoIr, fs.constants.F_OK).catch( + if (patchData.withIr) { + promises.push(fs.promises.access(patchData.urlOrthoIr, fs.constants.F_OK).catch( () => { - fs.promises.access(P.urlOrthoIrOrig, fs.constants.F_OK) + fs.promises.access(patchData.urlOrthoIrOrig, fs.constants.F_OK) // cas ou le patch sort du cache --> géré avec Opi .catch(() => {}); - P.withOrig = true; + patchData.withOrig = true; }, )); - promises.push(fs.promises.access(P.urlOpiIr, fs.constants.F_OK)); + promises.push(fs.promises.access(patchData.urlOpiRefIr, fs.constants.F_OK)); + if (isAuto) promises.push(fs.promises.access(patchData.urlOpiSecIr, fs.constants.F_OK)); } - return Promise.all(promises).then(() => P); + + return Promise.all(promises).then(() => patchData); } async function getPatches(req, _res, next) { @@ -194,34 +231,161 @@ async function getPatches(req, _res, next) { next(); } -async function applyPatch(pgClient, overviews, dirCache, idBranch, feature) { +function ozCppExe(patches, outputDir, geojsonPath) { + debug('>>ozCppExe'); + const arrArgsR = ['-r']; + const arrArgsS = ['-s']; + const arrArgsG = ['-g']; + const arrArgsO = ['-or']; + patches.forEach((patch) => { + arrArgsR.push(patch.withRgb ? patch.urlOpiRefRgb : patch.urlOpiRefIr); + arrArgsS.push(patch.withRgb ? patch.urlOpiSecRgb : patch.urlOpiSecIr); + arrArgsG.push(patch.withOrig ? patch.urlGraphOrig : patch.urlGraph); + if (patch.withRgb) { + arrArgsO.push(patch.withOrig ? patch.urlOrthoRgbOrig : patch.urlOrthoRgb); + } else { + arrArgsO.push(patch.withOrig ? patch.urlOrthoIrOrig : patch.urlOrthoIr); + } + }); + const arrArgs = [...arrArgsR, ...arrArgsS, ...arrArgsG, ...arrArgsO, '-p', geojsonPath]; + const options = { + weightDiffCost: 0.95, + weightTransition: 10, + minCost: 0.0001, + tension: 2, + border: 20, + outDir: outputDir, + }; + arrArgs.push( + '-w', `${options.weightDiffCost}`, + '-wt', `${options.weightTransition}`, + '-m', `${options.minCost}`, + '-t', `${options.tension}`, + '-b', `${options.border}`, + '-o', `${options.outDir}`, + '--verbose', + ); + return new Promise((res, rej) => { + execFile(ozExe, arrArgs, { env: { PROJ_LIB: process.env.PROJ_LIB } }, + (err, stdout) => { + debugOzcpp(stdout); + if (err) { + console.log(err); + rej(err); + } else { + res(`${stdout} OK`); + } + }); + }); +} + +// Maybe move that function to a specifique file.js file +// duplicate in regress\test-API\3_branch +function copyFileSync(source, target) { + let targetFile = target; + // If target is a directory, the copy will have the same name as the source + if (fs.existsSync(target)) { + if (fs.lstatSync(target).isDirectory()) { + targetFile = path.join(target, path.basename(source)); + } + } + fs.writeFileSync(targetFile, fs.readFileSync(source)); +} + +async function applyPatch(pgClient, overviews, dirCache, idBranch, geojson) { + const feature = geojson.features[0]; debug('applyPatch', feature); - const opi = (await db.getOPIFromName(pgClient, idBranch, feature.properties.opiName)); - const patchInserted = await db.insertPatch(pgClient, idBranch, feature.geometry, opi.id); + const opiRef = (await db.getOPIFromName(pgClient, idBranch, feature.properties.opiRef.name)); + // patch auto + let opiSec = { + id: null, + }; + const patchIsAuto = !!feature.properties.opiSec.name; + if (patchIsAuto) { + opiSec = (await db.getOPIFromName(pgClient, idBranch, feature.properties.opiSec.name)); + } + const patchInserted = await db.insertPatch(pgClient, idBranch, feature.geometry, + opiRef.id, opiSec.id, patchIsAuto); const patchId = patchInserted.id_patch; const newPatchNum = patchInserted.num; - const cogs = getCOGs(feature, overviews); + const geojsonPath = await gjson.writeGeojson(idBranch, patchId, dirCache, geojson); + + // in case of patch-auto, add border to bbox for selecting cogs + const borderMeters = patchIsAuto ? 20 : 0; + let { coordinates } = feature.geometry; + if (!patchIsAuto) { + [coordinates] = coordinates; + } + const cogs = getCOGs(coordinates, overviews, borderMeters); + const promisesCreatePatch = []; debug('~create patch'); cogs.forEach((aCog) => { promisesCreatePatch.push(createPatch(aCog, feature, - feature.properties.color, - feature.properties.opiName, - opi.with_rgb, - opi.with_ir, + feature.properties.opiRef.color, + feature.properties.opiRef.name, + feature.properties.opiSec.color, + feature.properties.opiSec.name, + opiRef.with_rgb, + opiRef.with_ir, overviews, dirCache, - idBranch)); + idBranch, + patchIsAuto)); }); debug('~Promise.all'); const slabsModified = []; - await Promise.all(promisesCreatePatch).then(async (patches) => { - const promises = []; - debug('~process patch'); + const patches = await Promise.all(promisesCreatePatch); + const promises = []; + debug('~process patch'); + + if (patchIsAuto) { + debug('~~Semi-auto patch'); + const urlOutputData = `${dirCache}/result_ozcpp_idBr${idBranch}`; + dirTmp = urlOutputData; + promises.push(ozCppExe(patches, urlOutputData, geojsonPath)); + + patches.forEach((patch) => { + if (patch === null) { + return; + } + const filename = (patch.withOrig ? '' : `${idBranch}_`) + patch.cogPath.filename; + /* eslint-disable no-param-reassign */ + patch.urlGraphOutput = path.join(urlOutputData, + 'out_graph', + `out_${filename}_georef.tif`); + if (patch.withRgb) { + debug(' >>>> RGB'); + patch.urlOrthoRgbOutput = path.join(urlOutputData, + 'out_ortho', + `out_${filename}_georef.tif`); + } + if (patch.withIr && patch.withRgb) { + debug(' >>>> RGB + IR'); + console.log('WARNING: Cache RGB + IR : le nouveau patch ne sera pas appliqué aux images IR'); + patch.urlOrthoIrOutput = path.join(dirCache, + 'ortho', patch.cogPath.dirPath, + `${idBranch}_${patch.cogPath.filename}_${newPatchNum}i.tif`); + + const urlOrthoIr = patch.withOrig ? patch.urlOrthoIrOrig : patch.urlOrthoIr; + copyFileSync(urlOrthoIr, patch.urlOrthoIrOutput); + } + if (patch.withIr && !patch.withRgb) { + debug(' >>>> IR seul'); + patch.urlOrthoIrOutput = path.join(urlOutputData, + 'out_ortho', + `out_${filename}_georef.tif`); + } + + /* eslint-enable no-param-reassign */ + slabsModified.push(patch.slab); + }); + } else { + debug('~~Polygon patch'); patches.forEach((patch) => { if (patch === null) { return; @@ -231,12 +395,15 @@ async function applyPatch(pgClient, overviews, dirCache, idBranch, feature) { 'graph', patch.cogPath.dirPath, `${idBranch}_${patch.cogPath.filename}_${newPatchNum}.tif`); + if (patch.withRgb) { + debug(' >>>> RGB'); patch.urlOrthoRgbOutput = path.join(dirCache, 'ortho', patch.cogPath.dirPath, `${idBranch}_${patch.cogPath.filename}_${newPatchNum}.tif`); } if (patch.withIr) { + debug(' >>>> IR'); patch.urlOrthoIrOutput = path.join(dirCache, 'ortho', patch.cogPath.dirPath, `${idBranch}_${patch.cogPath.filename}_${newPatchNum}i.tif`); @@ -245,84 +412,84 @@ async function applyPatch(pgClient, overviews, dirCache, idBranch, feature) { /* eslint-enable no-param-reassign */ slabsModified.push(patch.slab); - promises.push(gdalProcessing.processPatch(patch, overviews.tileSize.width)); + promises.push(gdalProcessing.processPatchAsync(patch, overviews.tileSize.width, + patchIsAuto)); }); - debug('', promises.length, 'patchs à appliquer.'); - await Promise.all(promises).then( - async () => { - // Tout c'est bien passé - debug("=> tout c'est bien passé on peut renommer les images"); - patches.forEach((patch) => { - if (patch === null) { - return; - } - const urlHistory = path.join(dirCache, - 'opi', - patch.cogPath.dirPath, - `${idBranch}_${patch.cogPath.filename}_history.packo`); - if (fs.existsSync(urlHistory)) { - debug('history existe'); - const history = `${fs.readFileSync(`${urlHistory}`)};${newPatchNum}`; - const tabHistory = history.split(';'); - const prevId = tabHistory[tabHistory.length - 2]; - - const urlGraphPrev = path.join(dirCache, 'graph', patch.cogPath.dirPath, - `${idBranch}_${patch.cogPath.filename}_${prevId}.tif`); - // on ne fait un rename que si prevId n'est pas 'orig' - if (prevId !== 'orig') { - rename(patch.urlGraph, urlGraphPrev); - } - - if (patch.withRgb) { - const urlOrthoRbgPrev = path.join(dirCache, 'ortho', patch.cogPath.dirPath, - `${idBranch}_${patch.cogPath.filename}_${prevId}.tif`); - // on ne fait un rename que si prevId n'est pas 'orig' - if (prevId !== 'orig') { - rename(patch.urlOrthoRgb, urlOrthoRbgPrev); - } - } - if (patch.withIr) { - const urlOrthoIrPrev = path.join(dirCache, 'ortho', patch.cogPath.dirPath, - `${idBranch}_${patch.cogPath.filename}_${prevId}i.tif`); - // on ne fait un rename que si prevId n'est pas 'orig' - if (prevId !== 'orig') { - rename(patch.urlOrthoIr, urlOrthoIrPrev); - } - } - debug(' historique :', history); - fs.writeFileSync(`${urlHistory}`, history); - } else { - debug('history n existe pas encore'); - const history = `orig;${newPatchNum}`; - fs.writeFileSync(`${urlHistory}`, history); - // On a pas besoin de renommer l'image d'origine - // qui reste partagée pour toutes les branches - } - rename(patch.urlGraphOutput, patch.urlGraph); - if (patch.withRgb) { - rename(patch.urlOrthoRgbOutput, patch.urlOrthoRgb); - } - if (patch.withIr) { - rename(patch.urlOrthoIrOutput, patch.urlOrthoIr); - } - }); - // on note le patch Id - /* eslint-disable-next-line */ + } + + debug('', promises.length, 'patchs à appliquer.'); + await Promise.all(promises); + // Tout c'est bien passé + debug("=> tout c'est bien passé on peut renommer les images"); + patches.forEach((patch) => { + if (patch === null) { + return; + } + const urlHistory = path.join(dirCache, + 'opi', + patch.cogPath.dirPath, + `${idBranch}_${patch.cogPath.filename}_history.packo`); + if (fs.existsSync(urlHistory)) { + debug('history existe'); + const history = `${fs.readFileSync(`${urlHistory}`)};${newPatchNum}`; + const tabHistory = history.split(';'); + const prevId = tabHistory[tabHistory.length - 2]; + + const urlGraphPrev = path.join(dirCache, 'graph', patch.cogPath.dirPath, + `${idBranch}_${patch.cogPath.filename}_${prevId}.tif`); + // on ne fait un rename que si prevId n'est pas 'orig' + if (prevId !== 'orig') { + rename(patch.urlGraph, urlGraphPrev); + } + + if (patch.withRgb) { + const urlOrthoRbgPrev = path.join(dirCache, 'ortho', patch.cogPath.dirPath, + `${idBranch}_${patch.cogPath.filename}_${prevId}.tif`); + // on ne fait un rename que si prevId n'est pas 'orig' + if (prevId !== 'orig') { + rename(patch.urlOrthoRgb, urlOrthoRbgPrev); + } + } + + if (patch.withIr) { + const urlOrthoIrPrev = path.join(dirCache, 'ortho', patch.cogPath.dirPath, + `${idBranch}_${patch.cogPath.filename}_${prevId}i.tif`); + // on ne fait un rename que si prevId n'est pas 'orig' + if (prevId !== 'orig') { + rename(patch.urlOrthoIr, urlOrthoIrPrev); + } + } + debug(' historique :', history); + fs.writeFileSync(`${urlHistory}`, history); + } else { + debug('history n\'existe pas encore'); + const history = `orig;${newPatchNum}`; + fs.writeFileSync(`${urlHistory}`, history); + // On a pas besoin de renommer l'image d'origine + // qui reste partagée pour toutes les branches + } + rename(patch.urlGraphOutput, patch.urlGraph); + if (patch.withRgb) { + rename(patch.urlOrthoRgbOutput, patch.urlOrthoRgb); + } + if (patch.withIr) { + rename(patch.urlOrthoIrOutput, patch.urlOrthoIr); + } + }); + // on note le patch Id + /* eslint-disable-next-line */ feature.properties.num = newPatchNum; - /* eslint-disable-next-line */ + /* eslint-disable-next-line */ feature.properties.slabs = slabsModified; - // on ajoute ce patch à l'historique - debug('=> Patch', newPatchNum, 'ajouté'); + // on ajoute ce patch à l'historique + debug('=> Patch', newPatchNum, 'ajouté'); - // ajouter les slabs correspondant au patch dans la table correspondante - await db.insertSlabs(pgClient, - patchId, - feature.properties.slabs); + // ajouter les slabs correspondant au patch dans la table correspondante + await db.insertSlabs(pgClient, + patchId, + feature.properties.slabs); - debug('on retourne les dalles modifiees -- 1 : ', slabsModified); - }, - ); - }); + debug('on retourne les dalles modifiees -- 1 : ', slabsModified); debug('Fin de applyPatch'); debug('on retourne les dalles modifiees -- 2 : ', slabsModified); return slabsModified; @@ -331,6 +498,7 @@ async function applyPatch(pgClient, overviews, dirCache, idBranch, feature) { async function postPatch(req, _res, next) { debug('>>POST patch'); if (req.error) { + debug(req.error); next(); return; } @@ -343,7 +511,7 @@ async function postPatch(req, _res, next) { overviews, req.dir_cache, idBranch, - geoJson.features[0]) + geoJson) .then((slabsModified) => { debug('slabsModified : ', slabsModified); req.result = { json: slabsModified, code: 200 }; @@ -358,6 +526,17 @@ async function postPatch(req, _res, next) { }) .finally(() => { debug('Fin de POST patch'); + if (fs.existsSync(dirTmp)) { + if (fs.lstatSync(dirTmp).isDirectory()) { + try { + fs.rmdirSync(dirTmp, { recursive: true, force: true }); + debug(`Suppression '${dirTmp}' OK`); + } catch (err) { + debug(err); + return; + } + } + } next(); }); } diff --git a/middlewares/wmts.js b/middlewares/wmts.js index 608690703..f851ad112 100644 --- a/middlewares/wmts.js +++ b/middlewares/wmts.js @@ -260,16 +260,22 @@ function wmts(req, _res, next) { } gdalProcessing.getTileEncoded(url, cogPath.x, cogPath.y, cogPath.z, - formatGDAL, overviews.tileSize.width, bands).then((img) => { - req.result = { img, code: 200 }; - next(); - }); + formatGDAL, overviews.tileSize.width, bands) + .then((img) => { + req.result = { img, code: 200 }; + next(); + }) + .catch((err) => { + // error not handled + console.warn('ERROR in gdalProcessing.getTileEncoded()', err); + }); } catch (error) { debug(error); - gdalProcessing.getDefaultEncoded(formatGDAL, overviews.tileSize.width).then((img) => { - req.result = { img, code: 200 }; - next(); - }); + gdalProcessing.getDefaultEncoded(formatGDAL, overviews.tileSize.width) + .then((img) => { + req.result = { img, code: 200 }; + next(); + }); } // GetFeatureInfo } else if (REQUEST === 'GetFeatureInfo') { @@ -302,12 +308,12 @@ function wmts(req, _res, next) { .then(async (color) => { debugFeatureInfo(color); let resCode = 200; - let opiName = ''; + let opiRefName = ''; try { const opi = await db.getOPIFromColor(req.client, idBranch, color); - opiName = opi.name; + opiRefName = opi.name; } catch (error) { - opiName = error.message; + opiRefName = error.message; resCode = 201; } const xmlResponse = '' @@ -317,7 +323,7 @@ function wmts(req, _res, next) { + ' xsi:schemaLocation="http://www.maps.bob/etopo2 GetFeatureInfoExampleSchema.xsd">' + '' + `<${LAYER}>` - + `${opiName}` + + `${opiRefName}` + `${color}` + `${TILEROW}` + `${TILECOL}` diff --git a/paramValidation/validator/index.js b/paramValidation/validator/index.js index e3d166b10..ea91d424d 100644 --- a/paramValidation/validator/index.js +++ b/paramValidation/validator/index.js @@ -1,7 +1,9 @@ const isCrs = require('./isCrs'); const isColor = require('./isColor'); +const isBool = require('./isBool'); module.exports = { isCrs, isColor, + isBool, }; diff --git a/paramValidation/validator/isBool.js b/paramValidation/validator/isBool.js new file mode 100644 index 000000000..a748acc0c --- /dev/null +++ b/paramValidation/validator/isBool.js @@ -0,0 +1,3 @@ +module.exports = function isBool(val) { + return (typeof val === 'boolean'); +}; diff --git a/regress/test-API/3_branch.js b/regress/test-API/3_branch.js index ce7b37e38..f9603b703 100644 --- a/regress/test-API/3_branch.js +++ b/regress/test-API/3_branch.js @@ -300,14 +300,18 @@ describe('route/branch.js', () => { { type: 'Feature', properties: { - color: overviews.list_OPI[testOpi].color, - opiName: testOpi, + colorRef: overviews.list_OPI[testOpi].color, + opiRefName: testOpi, + colorSec: 'none', + opiSecName: 'none', + patchIsAuto: false, }, geometry: { type: 'Polygon', coordinates: [[[230749, 6759646], [230752, 6759646], [230752, 6759644], [230749, 6759644], [230749, 6759646]]] }, }], }) .end((err, res) => { should.not.exist(err); + console.log(res.body.msg); res.should.have.status(200); const resJson = JSON.parse(res.text); resJson.should.be.a('array'); diff --git a/ressources/RGF93v2b_CC49_20cm.json b/ressources/RGF93v2b_CC49_20cm.json index 2de9ea553..a76d2ce77 100644 --- a/ressources/RGF93v2b_CC49_20cm.json +++ b/ressources/RGF93v2b_CC49_20cm.json @@ -1,5 +1,5 @@ { - "identifier": "RGF93v2b_CC49_5cm", + "identifier": "RGF93v2b_CC49_20cm", "crs": { "type": "EPSG", "code": 9849, diff --git a/ressources/RGF93v2b_LA93_20cm.json b/ressources/RGF93v2b_LA93_20cm.json index 583fa8321..3b8deef0d 100644 --- a/ressources/RGF93v2b_LA93_20cm.json +++ b/ressources/RGF93v2b_LA93_20cm.json @@ -1,5 +1,5 @@ { - "identifier": "RGF93v2b_LA93_5cm", + "identifier": "RGF93v2b_LA93_20cm", "crs": { "type": "EPSG", "code": 9794, diff --git a/ressources/example_macros_qgis.py b/ressources/example_macros_qgis.py index 252df2d5d..105a11e74 100644 --- a/ressources/example_macros_qgis.py +++ b/ressources/example_macros_qgis.py @@ -52,6 +52,7 @@ def closeProject(): opi_layer = None ortho_layer = None patch_layer = None +patch_layer_auto = None retinfo_layer = None retinfosauv_layer = None avancement_layer = None @@ -65,6 +66,8 @@ def closeProject(): ortho_layer = layer if (name == 'RETOUCHES_GRAPHE'): patch_layer = layer + if (name == 'RETOUCHES_GRAPHE_AUTO'): + patch_layer_auto = layer if (name == 'RETOUCHES_INFO'): retinfo_layer = layer if (name == 'RETOUCHES_INFO_SAUV'): @@ -142,6 +145,7 @@ def on_key(event): return patch_layer.startEditing() feature = list(patch_layer.getFeatures())[0] + ## TODO: handle patch auto mess = sendPatch(feature, OPI, color) print(mess) patch_layer.deleteFeature(feature.id()) @@ -154,6 +158,35 @@ def on_key(event): patch_layer.startEditing() return + if (touche == Qt.Key_A): + nb_features = patch_layer_auto.featureCount() + if nb_features == 0: + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText("PAS DE RETOUCHE") + msg.setWindowTitle("ERREUR") + msg.setStandardButtons(QMessageBox.Ok) + msg.exec_() + OPI = None + return + if nb_features > 1: + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText("UNE SEULE RETOUCHE A LA FOIS") + msg.setWindowTitle("ERREUR") + msg.setStandardButtons(QMessageBox.Ok) + msg.exec_() + OPI = None + return + patch_layer_auto.startEditing() + feature = list(patch_layer_auto.getFeatures())[0] + mPL=feature.geometry().asMultiPolyline() + firstLine=mPL[0] + sOPI1=selectOPI(firstLine[0].x(),firstLine[0].y()) + sOPI2=selectOPI(firstLine[1].x(),firstLine[1].y()) + print(sOPI1, sOPI2) + # TODO: handle patch auto + if (touche == Qt.Key_P): # Pick OPI lastPoint = iface.mapCanvas().mouseLastXY() diff --git a/routes/ozCppExe.js b/routes/ozCppExe.js new file mode 100644 index 000000000..8a348bfbe --- /dev/null +++ b/routes/ozCppExe.js @@ -0,0 +1,86 @@ +const debug = require('debug')('ozCpp'); +const router = require('express').Router(); +const { matchedData, query } = require('express-validator'); +const { execFile } = require('child_process'); + +const validateParams = require('../paramValidation/validateParams'); +const createErrMsg = require('../paramValidation/createErrMsg'); +const returnMsg = require('../middlewares/returnMsg'); + +const ozExe = process.env.OZEXE; + +router.get('/ozCppExe', [ + query('fstOpi') + .exists().withMessage(createErrMsg.missingParameter('fstOpi')), + query('secOpi') + .exists().withMessage(createErrMsg.missingParameter('secOpi')), + query('patch') + .exists().withMessage(createErrMsg.missingParameter('patch')), + query('graph') + .exists().withMessage(createErrMsg.missingParameter('graph')), + query('weightDiffCost') // value btwn 0 and 1 + .exists().withMessage(createErrMsg.missingParameter('weightDiffCost')), + query('weightTransition') + .exists().withMessage(createErrMsg.missingParameter('weightTransition')), + query('minCost') + .exists().withMessage(createErrMsg.missingParameter('minCost')), + query('tension') + .exists().withMessage(createErrMsg.missingParameter('tension')), + query('border') + .exists().withMessage(createErrMsg.missingParameter('border')), + query('outDir') + .exists().withMessage(createErrMsg.missingParameter('outDir')), +], validateParams, (req, res, next) => { + debug('~~~ozCppExe~~~'); + if (req.error) { + next(); + return; + } + const params = matchedData(req); + const { + fstOpi, secOpi, patch, graph, weightDiffCost, weightTransition, minCost, + tension, border, outDir, + } = params; + + console.log('Parameters: ', params); + + const arrArgs = ['-r']; + if (Array.isArray(fstOpi)) { + fstOpi.forEach((fstO) => { + arrArgs.push(fstO); + }); + } else arrArgs.push(fstOpi); + + arrArgs.push('-s'); + if (Array.isArray(secOpi)) { + secOpi.forEach((secO) => { + arrArgs.push(secO); + }); + } else arrArgs.push(secOpi); + + arrArgs.push('-p', `${patch}`); + + arrArgs.push('-g'); + if (Array.isArray(graph)) { + graph.forEach((gr) => { + arrArgs.push(gr); + }); + } else arrArgs.push(graph); + + arrArgs.push('-w', `${weightDiffCost}`, '-wt', `${weightTransition}`, '-m', `${minCost}`, + '-t', `${tension}`, '-b', `${border}`, '-o', `${outDir}`); + + execFile(ozExe, arrArgs, (err, stdout) => { + if (err) { + console.log(stdout); + console.log(err); + res.status(400).send(stdout + err); + } else { + console.log(stdout); + res.status(200).send(`${stdout} OK`); + } + }); +}, +returnMsg); + +module.exports = router; diff --git a/routes/patch.js b/routes/patch.js index eb9ded37b..87c021409 100644 --- a/routes/patch.js +++ b/routes/patch.js @@ -24,6 +24,11 @@ function encapBody(req, _res, next) { next(); } +const alias = { + opi1: 'opiRef', + opi2: 'opiSec', +}; + const geoJsonAPatcher = [ body('geoJSON') .exists().withMessage(createErrMsg.missingBody) @@ -41,14 +46,63 @@ const geoJsonAPatcher = [ .withMessage(createErrMsg.invalidParameter('crs')), body('geoJSON.features.*.geometry') .custom(GJV.isPolygon).withMessage(createErrMsg.InvalidEntite('geometry', 'polygon')), - body('geoJSON.features.*.properties.color') - .exists().withMessage(createErrMsg.missingParameter('properties.color')) + body('geoJSON.features.*.properties') + .exists().withMessage(createErrMsg.missingParameter('properties')), + + body(`geoJSON.features.*.properties.${alias.opi1}`) + .if(body('geoJSON.features.*.properties').exists()) + .exists().withMessage(createErrMsg.missingParameter(`properties.${alias.opi1}`)), + body(`geoJSON.features.*.properties.${alias.opi1}.name`) + .if(body(`geoJSON.features.*.properties.${alias.opi1}`).exists()) + .exists().withMessage(createErrMsg.missingParameter(`properties.${alias.opi1}.name`)) + .if(body(`geoJSON.features.*.properties.${alias.opi1}.name`).exists()) + .matches(/^[a-zA-Z0-9-_]+$/i) + .withMessage(createErrMsg.invalidParameter(`properties.${alias.opi1}.name`)), + body(`geoJSON.features.*.properties.${alias.opi1}.color`) + .if(body(`geoJSON.features.*.properties.${alias.opi1}`).exists()) + .exists().withMessage(createErrMsg.missingParameter(`properties.${alias.opi1}.color`)) + .if(body(`geoJSON.features.*.properties.${alias.opi1}.color`).exists()) + .custom(validator.isColor) + .withMessage(createErrMsg.invalidParameter(`properties.${alias.opi1}.color`)), + + // body(`geoJSON.features.*.properties.${aliasOpi2}`) + // .if(body('geoJSON.features.*.properties').exists()) + // .exists().withMessage(createErrMsg.missingParameter(`properties.${aliasOpi2}`)), + body(`geoJSON.features.*.properties.${alias.opi2}.name`) + .if(body(`geoJSON.features.*.properties.${alias.opi2}`).exists()) + .exists().withMessage(createErrMsg.missingParameter(`properties.${alias.opi2}.name`)) + .if(body(`geoJSON.features.*.properties.${alias.opi2}.name`).exists()) + .matches(/^[a-zA-Z0-9-_]+$/i) + .withMessage(createErrMsg.invalidParameter(`properties.${alias.opi2}.name`)), + body(`geoJSON.features.*.properties.${alias.opi2}.color`) + .if(body(`geoJSON.features.*.properties.${alias.opi2}`).exists()) + .exists().withMessage(createErrMsg.missingParameter(`properties.${alias.opi2}.color`)) + .if(body(`geoJSON.features.*.properties.${alias.opi2}.color`).exists()) + .custom(validator.isColor) + .withMessage(createErrMsg.invalidParameter(`properties.${alias.opi2}.color`)), + + // body('geoJSON.features.*.properties.colorRef') + // .exists().withMessage(createErrMsg.missingParameter('properties.colorRef')) + // .custom(validator.isColor) + // .withMessage(createErrMsg.invalidParameter('properties.colorRef')), + // body('geoJSON.features.*.properties.opiRefName') + // .exists().withMessage(createErrMsg.missingParameter('properties.opiRefName')) + // .matches(/^[a-zA-Z0-9-_]+$/i) + // .withMessage(createErrMsg.invalidParameter('properties.opiRefName')), + + /* body('geoJSON.features.*.properties.colorSec') + .exists().withMessage(createErrMsg.missingParameter('properties.colorSec')) .custom(validator.isColor) - .withMessage(createErrMsg.invalidParameter('properties.color')), - body('geoJSON.features.*.properties.opiName') - .exists().withMessage(createErrMsg.missingParameter('properties.opiName')) + .withMessage(createErrMsg.invalidParameter('properties.colorSec')), + body('geoJSON.features.*.properties.opiSecName') + .exists().withMessage(createErrMsg.missingParameter('properties.opiSecName')) .matches(/^[a-zA-Z0-9-_]+$/i) - .withMessage(createErrMsg.invalidParameter('properties.opiName')), + .withMessage(createErrMsg.invalidParameter('properties.opiSecName')), */ + // body('geoJSON.features.*.properties.patchIsAuto') + // .exists().withMessage(createErrMsg.missingParameter('properties.patchIsAuto')) + // .if(body('geoJSON.features.*.properties.patchIsAuto').exists()) + // .custom(validator.isBool) + // .withMessage(createErrMsg.invalidParameter('properties.patchIsAuto')), ]; router.get('/:idBranch/patches', diff --git a/serveur.js b/serveur.js index 6059b517c..0a7541a51 100644 --- a/serveur.js +++ b/serveur.js @@ -43,6 +43,7 @@ const branch = require('./routes/branch'); const cache = require('./routes/cache'); const vector = require('./routes/vector'); const processQueue = require('./routes/processQueue'); +const ozCpp = require('./routes/ozCppExe'); try { // desactive la mise en cache des images par le navigateur - OK Chrome/Chromium et Firefox @@ -81,6 +82,7 @@ try { app.use('/', cache); app.use('/', vector); app.use('/', processQueue); + app.use('/', ozCpp); app.use('/itowns', express.static('itowns')); diff --git a/sql/packo.sql b/sql/packo.sql index 329284ffb..d75f220b4 100644 --- a/sql/packo.sql +++ b/sql/packo.sql @@ -2,9 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 13.2 --- Dumped by pg_dump version 13.1 --- edited by F_Toromanoff +-- Dumped from database version 13.12 (Debian 13.12-1.pgdg110+1) +-- Dumped by pg_dump version 13.12 (Debian 13.12-1.pgdg110+1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -45,33 +44,28 @@ CREATE TYPE public.processes_status AS ENUM ( ALTER TYPE public.processes_status OWNER TO postgres; -- --- Name: processes; Type: TABLE; Schema: public; Owner: postgres +-- Name: auto_num_layers(); Type: FUNCTION; Schema: public; Owner: postgres -- -CREATE TABLE public.processes ( - id integer NOT NULL, - start_date timestamp with time zone NOT NULL, - end_date timestamp with time zone, - status public.processes_status DEFAULT 'running'::public.processes_status NOT NULL, - result character varying, - description character varying -); - - -ALTER TABLE public.processes OWNER TO postgres; +CREATE FUNCTION public.auto_num_layers() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.num = ( + SELECT + CASE WHEN max(num) IS NULL THEN 1 + ELSE max(num) + 1 + END next_num + FROM layers + WHERE + id_branch=NEW.id_branch + ); + RETURN NEW; +END; +$$; --- --- Name: processes_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres --- -ALTER TABLE public.processes ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.processes_id_seq - START WITH 0 - INCREMENT BY 1 - MINVALUE 0 - NO MAXVALUE - CACHE 1 -); +ALTER FUNCTION public.auto_num_layers() OWNER TO postgres; -- -- Name: auto_num_patches_and_delete_unactive(); Type: FUNCTION; Schema: public; Owner: postgres @@ -161,10 +155,6 @@ $$; ALTER FUNCTION public.create_orig_branch() OWNER TO postgres; -SET default_tablespace = ''; - -SET default_table_access_method = heap; - -- -- Name: create_remarks_layer(); Type: FUNCTION; Schema: public; Owner: postgres -- @@ -184,29 +174,9 @@ $$; ALTER FUNCTION public.create_remarks_layer() OWNER TO postgres; --- --- Name: auto_num_layers(); Type: FUNCTION; Schema: public; Owner: postgres --- - -CREATE FUNCTION public.auto_num_layers() RETURNS trigger - LANGUAGE plpgsql - AS $$ -BEGIN - NEW.num = ( - SELECT - CASE WHEN max(num) IS NULL THEN 1 - ELSE max(num) + 1 - END next_num - FROM layers - WHERE - id_branch=NEW.id_branch - ); - RETURN NEW; -END; -$$; - +SET default_tablespace = ''; -ALTER FUNCTION public.auto_num_layers() OWNER TO postgres; +SET default_table_access_method = heap; -- -- Name: branches; Type: TABLE; Schema: public; Owner: postgres @@ -266,29 +236,25 @@ ALTER TABLE public.caches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( -- --- Name: opi; Type: TABLE; Schema: public; Owner: postgres +-- Name: feature_ctrs; Type: TABLE; Schema: public; Owner: postgres -- -CREATE TABLE public.opi ( +CREATE TABLE public.feature_ctrs ( id integer NOT NULL, - id_cache integer NOT NULL, - date date, - time_ut time without time zone, - name character varying NOT NULL, - color smallint[] NOT NULL, - with_rgb boolean DEFAULT true NOT NULL, - with_ir boolean DEFAULT false NOT NULL + status boolean, + comment character varying, + id_feature integer NOT NULL ); -ALTER TABLE public.opi OWNER TO postgres; +ALTER TABLE public.feature_ctrs OWNER TO postgres; -- --- Name: opi_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- Name: feature_ctrs_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- -ALTER TABLE public.opi ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.opi_id_seq +ALTER TABLE public.feature_ctrs ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.feature_ctrs_id_seq START WITH 0 INCREMENT BY 1 MINVALUE 0 @@ -298,26 +264,25 @@ ALTER TABLE public.opi ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( -- --- Name: slabs; Type: TABLE; Schema: public; Owner: postgres +-- Name: features; Type: TABLE; Schema: public; Owner: postgres -- -CREATE TABLE public.slabs ( +CREATE TABLE public.features ( id integer NOT NULL, - id_patch integer NOT NULL, - x integer NOT NULL, - y integer NOT NULL, - z integer NOT NULL + geom public.geometry NOT NULL, + properties character varying, + id_layer integer NOT NULL ); -ALTER TABLE public.slabs OWNER TO postgres; +ALTER TABLE public.features OWNER TO postgres; -- --- Name: slabs_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- Name: features_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- -ALTER TABLE public.slabs ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.slabs_id_seq +ALTER TABLE public.features ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.features_id_seq START WITH 0 INCREMENT BY 1 MINVALUE 0 @@ -327,27 +292,57 @@ ALTER TABLE public.slabs ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( -- --- Name: patches; Type: TABLE; Schema: public; Owner: postgres +-- Name: layers; Type: TABLE; Schema: public; Owner: postgres -- -CREATE TABLE public.patches ( +CREATE TABLE public.layers ( id integer NOT NULL, + name character varying NOT NULL, num integer NOT NULL, - geom public.geometry NOT NULL, + crs character varying NOT NULL, id_branch integer NOT NULL, - active boolean DEFAULT true NOT NULL, - id_opi integer NOT NULL + id_style integer NOT NULL ); -ALTER TABLE public.patches OWNER TO postgres; +ALTER TABLE public.layers OWNER TO postgres; -- --- Name: patches_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- Name: features_json; Type: VIEW; Schema: public; Owner: postgres -- -ALTER TABLE public.patches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.patches_id_seq +CREATE VIEW public.features_json AS + SELECT l.id AS id_layer, + COALESCE(feature_json.features, '[]'::jsonb) AS features + FROM (public.layers l + LEFT JOIN ( SELECT t.id_layer, + jsonb_agg(jsonb_build_object('type', 'Feature', 'geometry', (public.st_asgeojson(t.geom, 9, 0))::jsonb, 'properties', ((to_jsonb(t.*) - 'id_layer'::text) - 'geom'::text))) AS features + FROM ( SELECT t1.id_layer, + t1.id, + t1.geom, + t1.properties, + t1.status, + t1.comment + FROM ( SELECT f.id_layer, + f.id, + f.geom, + f.properties, + fc.status, + fc.comment + FROM (public.features f + LEFT JOIN public.feature_ctrs fc ON ((f.id = fc.id_feature))) + ORDER BY f.id) t1) t + GROUP BY t.id_layer) feature_json ON ((l.id = feature_json.id_layer))); + + +ALTER TABLE public.features_json OWNER TO postgres; + +-- +-- Name: layers_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.layers ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.layers_id_seq START WITH 0 INCREMENT BY 1 MINVALUE 0 @@ -357,27 +352,61 @@ ALTER TABLE public.patches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( -- --- Name: layers; Type: TABLE; Schema: public; Owner: postgres +-- Name: opi; Type: TABLE; Schema: public; Owner: postgres -- -CREATE TABLE public.layers ( +CREATE TABLE public.opi ( id integer NOT NULL, + id_cache integer NOT NULL, + date date, + time_ut time without time zone, name character varying NOT NULL, + color smallint[] NOT NULL, + with_rgb boolean DEFAULT true NOT NULL, + with_ir boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.opi OWNER TO postgres; + +-- +-- Name: opi_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.opi ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.opi_id_seq + START WITH 0 + INCREMENT BY 1 + MINVALUE 0 + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: patches; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.patches ( + id integer NOT NULL, num integer NOT NULL, - crs character varying NOT NULL, + geom public.geometry NOT NULL, id_branch integer NOT NULL, - id_style integer NOT NULL + active boolean DEFAULT true NOT NULL, + id_opi integer NOT NULL, + id_opisec integer, + is_auto boolean DEFAULT false NOT NULL ); -ALTER TABLE public.layers OWNER TO postgres; +ALTER TABLE public.patches OWNER TO postgres; -- --- Name: layers_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- Name: patches_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- -ALTER TABLE public.layers ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.layers_id_seq +ALTER TABLE public.patches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.patches_id_seq START WITH 0 INCREMENT BY 1 MINVALUE 0 @@ -385,26 +414,29 @@ ALTER TABLE public.layers ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( CACHE 1 ); + -- --- Name: features; Type: TABLE; Schema: public; Owner: postgres +-- Name: processes; Type: TABLE; Schema: public; Owner: postgres -- -CREATE TABLE public.features ( +CREATE TABLE public.processes ( id integer NOT NULL, - geom public.geometry NOT NULL, - properties character varying, - id_layer integer NOT NULL + start_date timestamp with time zone NOT NULL, + end_date timestamp with time zone, + status public.processes_status DEFAULT 'running'::public.processes_status NOT NULL, + result character varying, + description character varying ); -ALTER TABLE public.features OWNER TO postgres; +ALTER TABLE public.processes OWNER TO postgres; -- --- Name: features_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- Name: processes_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- -ALTER TABLE public.features ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.features_id_seq +ALTER TABLE public.processes ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.processes_id_seq START WITH 0 INCREMENT BY 1 MINVALUE 0 @@ -414,25 +446,26 @@ ALTER TABLE public.features ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( -- --- Name: feature_ctrs; Type: TABLE; Schema: public; Owner: postgres +-- Name: slabs; Type: TABLE; Schema: public; Owner: postgres -- -CREATE TABLE public.feature_ctrs ( +CREATE TABLE public.slabs ( id integer NOT NULL, - status boolean, - comment character varying, - id_feature integer NOT NULL + id_patch integer NOT NULL, + x integer NOT NULL, + y integer NOT NULL, + z integer NOT NULL ); -ALTER TABLE public.feature_ctrs OWNER TO postgres; +ALTER TABLE public.slabs OWNER TO postgres; -- --- Name: feature_ctrs_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- Name: slabs_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- -ALTER TABLE public.feature_ctrs ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.feature_ctrs_id_seq +ALTER TABLE public.slabs ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.slabs_id_seq START WITH 0 INCREMENT BY 1 MINVALUE 0 @@ -469,6 +502,11 @@ ALTER TABLE public.styles ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( CACHE 1 ); +-- +-- Data for Name: styles; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +INSERT INTO public.styles OVERRIDING SYSTEM VALUE VALUES (0, 'Remarques', 1, true, '{"fill": {"color": "#ee6d03", "opacity": 0.7}, "point": {"color": "#ee6d03", "radius": 5}, "stroke": {"color": "#ee6d03"}}'); -- -- Name: branches branches_name_id_cache_key; Type: CONSTRAINT; Schema: public; Owner: postgres @@ -503,103 +541,103 @@ ALTER TABLE ONLY public.caches -- --- Name: opi opi_color_id_cache_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: feature_ctrs feature_ctrs_id_feature_key; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.opi - ADD CONSTRAINT opi_color_id_cache_key UNIQUE (color, id_cache); +ALTER TABLE ONLY public.feature_ctrs + ADD CONSTRAINT feature_ctrs_id_feature_key UNIQUE (id_feature); -- --- Name: opi opi_name_id_cache_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: feature_ctrs feature_ctrs_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.opi - ADD CONSTRAINT opi_name_id_cache_key UNIQUE (name, id_cache); +ALTER TABLE ONLY public.feature_ctrs + ADD CONSTRAINT feature_ctrs_pkey PRIMARY KEY (id); -- --- Name: opi opi_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: features features_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.opi - ADD CONSTRAINT opi_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.features + ADD CONSTRAINT features_pkey PRIMARY KEY (id); -- --- Name: patches patches_num_id_branch_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: layers layers_name_id_branch_key; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.patches - ADD CONSTRAINT patches_num_id_branch_key UNIQUE (num, id_branch); +ALTER TABLE ONLY public.layers + ADD CONSTRAINT layers_name_id_branch_key UNIQUE (name, id_branch); -- --- Name: patches patches_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: layers layers_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.patches - ADD CONSTRAINT patches_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.layers + ADD CONSTRAINT layers_pkey PRIMARY KEY (id); -- --- Name: slabs slabs_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: opi opi_color_id_cache_key; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.slabs - ADD CONSTRAINT slabs_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.opi + ADD CONSTRAINT opi_color_id_cache_key UNIQUE (color, id_cache); -- --- Name: layers layers_name_id_branch_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: opi opi_name_id_cache_key; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.layers - ADD CONSTRAINT layers_name_id_branch_key UNIQUE (name, id_branch); +ALTER TABLE ONLY public.opi + ADD CONSTRAINT opi_name_id_cache_key UNIQUE (name, id_cache); -- --- Name: layers layers_num_id_branch_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: opi opi_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- --- ALTER TABLE ONLY public.layers --- ADD CONSTRAINT layers_num_id_branch_key UNIQUE (num, id_branch); +ALTER TABLE ONLY public.opi + ADD CONSTRAINT opi_pkey PRIMARY KEY (id); -- --- Name: layers layers_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: patches patches_num_id_branch_key; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.layers - ADD CONSTRAINT layers_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.patches + ADD CONSTRAINT patches_num_id_branch_key UNIQUE (num, id_branch); -- --- Name: features features_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: patches patches_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.features - ADD CONSTRAINT features_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.patches + ADD CONSTRAINT patches_pkey PRIMARY KEY (id); -- --- Name: feature_ctrs feature_ctrs_id_feature_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: processes processes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.feature_ctrs - ADD CONSTRAINT feature_ctrs_id_feature_key UNIQUE (id_feature); +ALTER TABLE ONLY public.processes + ADD CONSTRAINT processes_pkey PRIMARY KEY (id); -- --- Name: feature_ctrs feature_ctrs_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: slabs slabs_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.feature_ctrs - ADD CONSTRAINT feature_ctrs_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.slabs + ADD CONSTRAINT slabs_pkey PRIMARY KEY (id); -- --- Name: styles styles_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: styles styles_name; Type: CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.styles @@ -615,25 +653,24 @@ ALTER TABLE ONLY public.styles -- --- Name: caches processes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- Name: fki_patches_id_opisec_fkey; Type: INDEX; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.processes - ADD CONSTRAINT processes_pkey PRIMARY KEY (id); +CREATE INDEX fki_patches_id_opisec_fkey ON public.patches USING btree (id_opisec); -- --- Name: patches auto_num_patches_and_delete_unactive_on_insert; Type: TRIGGER; Schema: public; Owner: postgres +-- Name: layers auto_num_layers; Type: TRIGGER; Schema: public; Owner: postgres -- -CREATE TRIGGER auto_num_patches_and_delete_unactive_on_insert BEFORE INSERT ON public.patches FOR EACH ROW EXECUTE FUNCTION public.auto_num_patches_and_delete_unactive(); +CREATE TRIGGER auto_num_layers BEFORE INSERT ON public.layers FOR EACH ROW EXECUTE FUNCTION public.auto_num_layers(); -- --- Name: caches insert_newcache; Type: TRIGGER; Schema: public; Owner: postgres +-- Name: patches auto_num_patches_and_delete_unactive_on_insert; Type: TRIGGER; Schema: public; Owner: postgres -- -CREATE TRIGGER insert_newcache AFTER INSERT ON public.caches FOR EACH ROW EXECUTE FUNCTION public.create_orig_branch(); +CREATE TRIGGER auto_num_patches_and_delete_unactive_on_insert BEFORE INSERT ON public.patches FOR EACH ROW EXECUTE FUNCTION public.auto_num_patches_and_delete_unactive(); -- @@ -644,27 +681,27 @@ CREATE TRIGGER insert_newbranch AFTER INSERT ON public.branches FOR EACH ROW EXE -- --- Name: patches on_patch_activation; Type: TRIGGER; Schema: public; Owner: postgres +-- Name: caches insert_newcache; Type: TRIGGER; Schema: public; Owner: postgres -- -CREATE TRIGGER on_patch_activation BEFORE UPDATE OF active ON public.patches FOR EACH ROW WHEN ((new.active = true)) EXECUTE FUNCTION public.check_before_patch_activation(); +CREATE TRIGGER insert_newcache AFTER INSERT ON public.caches FOR EACH ROW EXECUTE FUNCTION public.create_orig_branch(); -- --- Name: patches on_patch_deactivation; Type: TRIGGER; Schema: public; Owner: postgres +-- Name: patches on_patch_activation; Type: TRIGGER; Schema: public; Owner: postgres -- -CREATE TRIGGER on_patch_deactivation BEFORE UPDATE OF active ON public.patches FOR EACH ROW WHEN ((new.active = false)) EXECUTE FUNCTION public.check_before_patch_deactivation(); +CREATE TRIGGER on_patch_activation BEFORE UPDATE OF active ON public.patches FOR EACH ROW WHEN ((new.active = true)) EXECUTE FUNCTION public.check_before_patch_activation(); -- --- Name: layers auto_num_layers; Type: TRIGGER; Schema: public; Owner: postgres +-- Name: patches on_patch_deactivation; Type: TRIGGER; Schema: public; Owner: postgres -- -CREATE TRIGGER auto_num_layers BEFORE INSERT ON public.layers FOR EACH ROW EXECUTE FUNCTION public.auto_num_layers(); +CREATE TRIGGER on_patch_deactivation BEFORE UPDATE OF active ON public.patches FOR EACH ROW WHEN ((new.active = false)) EXECUTE FUNCTION public.check_before_patch_deactivation(); ----- +-- -- Name: branches branches_id_cache_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- @@ -673,35 +710,19 @@ ALTER TABLE ONLY public.branches -- --- Name: opi opi_id_cache_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.opi - ADD CONSTRAINT opi_id_cache_fkey FOREIGN KEY (id_cache) REFERENCES public.caches(id) ON DELETE CASCADE NOT VALID; - - --- --- Name: patches patches_id_branch_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.patches - ADD CONSTRAINT patches_id_branch_fkey FOREIGN KEY (id_branch) REFERENCES public.branches(id) ON DELETE CASCADE NOT VALID; - - --- --- Name: patches patches_id_opi_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- Name: feature_ctrs feature_ctrs_id_feature_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.patches - ADD CONSTRAINT patches_id_opi_fkey FOREIGN KEY (id_opi) REFERENCES public.opi(id) NOT VALID; +ALTER TABLE ONLY public.feature_ctrs + ADD CONSTRAINT feature_ctrs_id_feature_fkey FOREIGN KEY (id_feature) REFERENCES public.features(id) ON DELETE CASCADE NOT VALID; -- --- Name: slabs slabs_id_patch_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- Name: features features_id_layer_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.slabs - ADD CONSTRAINT slabs_id_patch_fkey FOREIGN KEY (id_patch) REFERENCES public.patches(id) ON DELETE CASCADE NOT VALID; +ALTER TABLE ONLY public.features + ADD CONSTRAINT features_id_layer_fkey FOREIGN KEY (id_layer) REFERENCES public.layers(id) ON DELETE CASCADE NOT VALID; -- @@ -721,59 +742,45 @@ ALTER TABLE ONLY public.layers -- --- Name: features features_id_layer_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgresn +-- Name: opi opi_id_cache_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.features - ADD CONSTRAINT features_id_layer_fkey FOREIGN KEY (id_layer) REFERENCES public.layers(id) ON DELETE CASCADE NOT VALID; +ALTER TABLE ONLY public.opi + ADD CONSTRAINT opi_id_cache_fkey FOREIGN KEY (id_cache) REFERENCES public.caches(id) ON DELETE CASCADE NOT VALID; -- --- Name: feature_ctrs feature_ctrs_id_feature_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- Name: patches patches_id_branch_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- -ALTER TABLE ONLY public.feature_ctrs - ADD CONSTRAINT feature_ctrs_id_feature_fkey FOREIGN KEY (id_feature) REFERENCES public.features(id) ON DELETE CASCADE NOT VALID; +ALTER TABLE ONLY public.patches + ADD CONSTRAINT patches_id_branch_fkey FOREIGN KEY (id_branch) REFERENCES public.branches(id) ON DELETE CASCADE NOT VALID; -- --- PostgreSQL database dump complete +-- Name: patches patches_id_opi_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- +ALTER TABLE ONLY public.patches + ADD CONSTRAINT patches_id_opi_fkey FOREIGN KEY (id_opi) REFERENCES public.opi(id) NOT VALID; + -- --- Name: features_json; Type: VIEW; Schema: public; Owner: postgres +-- Name: patches patches_id_opisec_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- -CREATE VIEW public.features_json AS - SELECT l.id AS id_layer, - COALESCE(feature_json.features, '[]'::jsonb) AS features - FROM (public.layers l - LEFT JOIN ( SELECT t.id_layer, - jsonb_agg(jsonb_build_object('type', 'Feature', 'geometry', (public.st_asgeojson(t.geom,9,0))::jsonb, 'properties', ((to_jsonb(t.*) - 'id_layer'::text) - 'geom'::text))) AS features - FROM ( SELECT t1.id_layer, - t1.id, - t1.geom, - t1.properties, - t1.status, - t1.comment - FROM ( SELECT f.id_layer, - f.id, - f.geom, - f.properties, - fc.status, - fc.comment - FROM (public.features f - LEFT JOIN public.feature_ctrs fc ON ((f.id = fc.id_feature))) - ORDER BY f.id) t1) t - GROUP BY t.id_layer) feature_json ON ((l.id = feature_json.id_layer))); +ALTER TABLE ONLY public.patches + ADD CONSTRAINT patches_id_opisec_fkey FOREIGN KEY (id_opisec) REFERENCES public.opi(id) NOT VALID; -ALTER TABLE public.features_json OWNER TO postgres; +-- +-- Name: slabs slabs_id_patch_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.slabs + ADD CONSTRAINT slabs_id_patch_fkey FOREIGN KEY (id_patch) REFERENCES public.patches(id) ON DELETE CASCADE NOT VALID; -- --- Import données base Packo +-- PostgreSQL database dump complete -- - -INSERT INTO public.styles (name,opacity,visibility,style_itowns) VALUES ('Remarques',1,true,'{"fill": {"color": "#ee6d03", "opacity": 0.7}, "point": {"color": "#ee6d03", "radius": 5}, "stroke": {"color": "#ee6d03"}}') returning id;