├── .dockerignore ├── .env ├── .eslint.config.js ├── .github └── workflows │ ├── ci.yml │ └── docker-publish.yml ├── .gitignore ├── .mocharc.json ├── .nvmrc ├── Dockerfile ├── LICENCE.md ├── README.md ├── app.js ├── checker ├── index.js ├── isCodeInsee.js └── isGeometry.js ├── circle.yml ├── controllers ├── aoc │ └── index.js ├── bduni │ └── index.js ├── cadastre │ └── index.js ├── codes-postaux │ └── index.js ├── corse │ └── index.js ├── er │ └── index.js ├── gpu │ └── index.js ├── health │ └── index.js ├── limites-administratives │ └── index.js ├── nature │ └── index.js ├── rpg │ └── index.js └── wfs-geoportail │ └── index.js ├── data └── .gitkeep ├── datasets ├── appellations-viticoles │ └── config.js ├── base-adresse-nationale │ └── config.js ├── geoportail │ └── config.js ├── gpu │ └── config.js └── index.js ├── dev └── docker.md ├── doc ├── aoc.yml ├── bduni.yml ├── cadastre.yml ├── codes-postaux.yml ├── corse.yml ├── er.yml ├── faq │ └── calcul-surface.md ├── gpu.yml ├── limites-administratives.yml ├── nature.yml ├── pdf │ ├── docUser_moduleAoc.pdf │ ├── docUser_moduleCadastre.pdf │ ├── docUser_moduleCodesPostaux.pdf │ ├── docUser_moduleNature.pdf │ ├── docUser_moduleRPG.pdf │ ├── docUser_moduleUrbanisme.pdf │ └── docUser_moduleWfsGeoportail.pdf ├── rpg.yml ├── views │ ├── index.ejs │ ├── mentions.ejs │ ├── module.ejs │ └── partial │ │ ├── head.ejs │ │ ├── menu.ejs │ │ └── scripts.ejs └── wfs-geoportail.yml ├── docker-compose.yml ├── helper └── parseInseeCode.js ├── lib ├── ClientBduni.js ├── ClientEr.js ├── buildBduniCqlFilter.js ├── buildErCqlFilter.js ├── cql_filter.js └── httpClient.js ├── middlewares ├── aocWfsClient.js ├── bduniWfsClient.js ├── drealCorseWfsClient.js ├── erWfsClient.js ├── gppWfsClient.js ├── gpuWfsClient.js ├── naturegppWfsClient.js ├── pgClient.js ├── request-logger.js ├── ressources_cle_wfs2022-05-20.csv └── validateParams.js ├── package-lock.json ├── package.json ├── processes.json.sample ├── server.js └── test ├── checker ├── isCodeInsee.js └── isGeometry.js ├── controllers ├── bduni │ └── troncon.js ├── cadastre │ ├── commune.js │ ├── division.js │ ├── localisant.js │ └── parcelle.js ├── codes-postaux │ └── communes.js ├── er │ ├── category.js │ ├── grid.js │ └── product.js ├── gpu │ ├── all.js │ ├── document.js │ ├── municipality.js │ └── zone-urba.js ├── limites-administratives │ ├── commune.js │ ├── departement.js │ └── region.js └── rpg │ ├── rpgv1.js │ └── rpgv2.js └── helper └── parseInseeCode.js /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.git/ 3 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | HOST_HOSTNAME=dev.localhost 2 | POSTGRES_PASSWORD=postgis 3 | -------------------------------------------------------------------------------- /.eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | 3 | export default [ 4 | { 5 | "rules": { 6 | "indent": [ 7 | 2, 8 | 4 9 | ], 10 | "quotes": [ 11 | 2, 12 | "single" 13 | ], 14 | "linebreak-style": [ 15 | 2, 16 | "unix" 17 | ], 18 | "semi": [ 19 | 2, 20 | "always" 21 | ], 22 | "no-console": [ 23 | 1 24 | ] 25 | }, 26 | languageOptions: { 27 | sourceType: "module", 28 | globals: { 29 | ...globals.node, 30 | ...globals.browser 31 | } 32 | } 33 | } 34 | ] -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [20.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | - run: npm ci 26 | - run: npm run build --if-present 27 | - run: npm test 28 | 29 | - name: Publish coverage to coveralls.io 30 | run: npm run coveralls 31 | env: 32 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | #schedule: 10 | # - cron: '40 18 * * *' 11 | push: 12 | branches: [ "master" ] 13 | # Publish semver tags as releases. 14 | tags: [ 'v*.*.*' ] 15 | pull_request: 16 | branches: [ "master" ] 17 | 18 | env: 19 | # Use docker.io for Docker Hub if empty 20 | REGISTRY: ghcr.io 21 | # github.repository as / 22 | IMAGE_NAME: ${{ github.repository }} 23 | 24 | 25 | jobs: 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: read 31 | packages: write 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v3 36 | 37 | # Workaround: https://github.com/docker/build-push-action/issues/461 38 | - name: Setup Docker buildx 39 | uses: docker/setup-buildx-action@v2 40 | 41 | # Login against a Docker registry except on PR 42 | # https://github.com/docker/login-action 43 | - name: Log into registry ${{ env.REGISTRY }} 44 | if: github.event_name != 'pull_request' 45 | uses: docker/login-action@v2 46 | with: 47 | registry: ${{ env.REGISTRY }} 48 | username: ${{ github.actor }} 49 | password: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | # Extract metadata (tags, labels) for Docker 52 | # https://github.com/docker/metadata-action 53 | - name: Extract Docker metadata 54 | id: meta 55 | uses: docker/metadata-action@v3 56 | with: 57 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 58 | 59 | # Build and push Docker image with Buildx (don't push on PR) 60 | # https://github.com/docker/build-push-action 61 | - name: Build and push Docker image 62 | id: build-and-push 63 | uses: docker/build-push-action@v3 64 | with: 65 | context: . 66 | push: ${{ github.event_name != 'pull_request' }} 67 | tags: ${{ steps.meta.outputs.tags }} 68 | labels: ${{ steps.meta.outputs.labels }} 69 | cache-from: type=gha 70 | cache-to: type=gha,mode=max 71 | 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | 3 | data/ 4 | node_modules/ 5 | coverage/ 6 | .nyc_output/ 7 | 8 | *.log 9 | tmp 10 | .DS_Store 11 | 12 | processes.json 13 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": "spec", 3 | "recursive": true, 4 | "ui": "bdd", 5 | "timeout": "120000" 6 | } -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v6 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | 3 | ENV PGHOST=db 4 | ENV PGDATABASE=apicarto 5 | ENV PGUSER=postgres 6 | ENV PGPASSWORD=postgis 7 | ENV PGPORT=5432 8 | 9 | COPY --chown=node:node . /opt/apicarto 10 | 11 | WORKDIR /opt/apicarto 12 | USER node 13 | RUN npm install 14 | 15 | EXPOSE 8091 16 | CMD ["node", "server.js"] 17 | 18 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | # API Carto 2 | 3 | This software is released under the licence CeCILL-B (Free BSD compatible) 4 | 5 | You may obtain a copy of the License at : 6 | 7 | http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt (english) 8 | 9 | http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt (french) 10 | 11 | see http://www.cecill.info/ 12 | 13 | Copyright (c) 2017 IGN -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # APICarto 2 | 3 | [![CI](https://github.com/IGNF/apicarto/actions/workflows/ci.yml/badge.svg)](https://github.com/IGNF/apicarto/actions/workflows/ci.yml) 4 | [![Coverage Status](https://coveralls.io/repos/github/IGNF/apicarto/badge.svg?branch=master)](https://coveralls.io/github/IGNF/apicarto?branch=master) 5 | 6 | ## Prérequis 7 | 8 | Pour faire fonctionner API Carto, vous avez besoin de: 9 | 10 | * [Node.js](https://nodejs.org) v20+ 11 | 12 | ### Prérequis module aoc 13 | 14 | Pour faire fonctionner le module aoc, vous avez besoin en plus de: 15 | 16 | * PostgreSQL v12+ 17 | * PostGIS v2.2+ 18 | * [ogr2ogr](http://www.gdal.org/ogr2ogr.html) v1.11+ 19 | * wget (inclus dans la plupart des distributions Linux) 20 | 21 | 22 | ## Variables d'environnements 23 | 24 | ### Configuration de la connexion postgresql pour le module aoc 25 | 26 | La connexion à la base postgresql est configurée à l'aide des variables d'environnement standard postgresql : 27 | 28 | | Variable | Description | 29 | |------------|-------------------------------| 30 | | PGHOST | Host du serveur postgresql | 31 | | PGDATABASE | Nom de la base de données | 32 | | PGUSER | Nom de l'utilisateur | 33 | | PGPASSWORD | Mot de passe de l'utilisateur | 34 | | PGPORT | Host du serveur postgresql | 35 | 36 | ## Sources de données 37 | 38 | | Source | Version | Modules | Plus d'information | 39 | |-----------------------|--------------------|-------------------|--------------------| 40 | | Géoplateforme | Flux WFS | Cadastre
RPG
Nature
WFS-Geoportail | [Geoservices](https://geoservices.ign.fr/services-web-experts) | 41 | | GPU | Flux WFS | GPU | [Géoportail de l'urbanisme](https://www.geoportail-urbanisme.gouv.fr/) | 42 | | Base adresse nationale | v4.1.1 | Codes Postaux | [BAN](https://github.com/baseadressenationale/codes-postaux) | 43 | 44 | 45 | 46 | ## Installation 47 | 48 | ### Installation du package 49 | 50 | ``` 51 | npm install 52 | ``` 53 | 54 | ### Lancer le service 55 | 56 | ``` 57 | npm start 58 | ``` 59 | 60 | ## Installation complémentaire pour le module aoc 61 | 62 | 63 | Sous Ubuntu : 64 | ```bash 65 | # Installer ogr2ogr 66 | apt-get install gdal-bin 67 | 68 | # Installer PostgreSQL et PostGIS 69 | apt-get postgresql postgis postgresql-13-postgis-3 70 | ``` 71 | 72 | Sous Mac OS X : 73 | ```bash 74 | # Installer ogr2ogr 75 | brew install gdal 76 | 77 | # Installer PostgreSQL et PostGIS 78 | brew install postgresql postgis 79 | ``` 80 | 81 | ### Création de la base de données 82 | 83 | Voir https://gitlab.gpf-tech.ign.fr/apicarto/apicarto-integration 84 | 85 | 86 | ## Développement derrière un proxy 87 | 88 | En cas de nécessité, utiliser les [variables d'environnement standards](https://www.npmjs.com/package/request#controlling-proxy-behaviour-using-environment-variables). 89 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | import express from 'express'; 4 | import bodyParser from 'body-parser'; 5 | import cors from 'cors'; 6 | 7 | import { requestLogger } from './middlewares/request-logger.js'; 8 | 9 | import { router as cadastre } from './controllers/cadastre/index.js'; 10 | import { router as limites_administratives} from './controllers/limites-administratives/index.js'; 11 | import { router as aoc } from './controllers/aoc/index.js'; 12 | import { router as codes_postaux } from './controllers/codes-postaux/index.js'; 13 | import { router as gpu } from './controllers/gpu/index.js'; 14 | import { router as rpg } from './controllers/rpg/index.js'; 15 | import { router as nature } from './controllers/nature/index.js'; 16 | import { router as wfs_geoportail } from './controllers/wfs-geoportail/index.js'; 17 | import { router as er } from './controllers/er/index.js'; 18 | import { router as bduni } from './controllers/bduni/index.js'; 19 | import { router as corse } from './controllers/corse/index.js'; 20 | import { router as health } from './controllers/health/index.js'; 21 | 22 | import { datasets } from './datasets/index.js'; 23 | 24 | const __filename = fileURLToPath(import.meta.url); 25 | 26 | const __dirname = path.dirname(__filename); 27 | 28 | 29 | var app = express(); 30 | 31 | /* Mentions légales */ 32 | app.get('/api/doc/mentions', function(req,res){ 33 | res.render('mentions'); 34 | }); 35 | 36 | /*------------------------------------------------------------------------------ 37 | * common middlewares 38 | ------------------------------------------------------------------------------*/ 39 | 40 | var env = process.env.NODE_ENV; 41 | 42 | if (env === 'production') { 43 | // see http://expressjs.com/fr/guide/behind-proxies.html 44 | app.enable('trust proxy'); 45 | } 46 | 47 | app.use(bodyParser.json()); 48 | app.use(cors()); 49 | 50 | app.use(function (req, res, next) { 51 | res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); 52 | res.header('Expires', '-1'); 53 | res.header('Pragma', 'no-cache'); 54 | next(); 55 | }); 56 | 57 | app.use(requestLogger()); 58 | 59 | /*------------------------------------------------------------------------------ 60 | * /api/doc - expose documentation 61 | -----------------------------------------------------------------------------*/ 62 | 63 | app.set('view engine', 'ejs'); 64 | app.set('views', path.join(__dirname, '/doc/views')); 65 | app.use( 66 | '/api/doc/vendor/swagger-ui', 67 | express.static(__dirname + '/node_modules/swagger-ui-dist') 68 | ); 69 | app.use('/api/doc', express.static(__dirname + '/doc')); 70 | app.get('/api/doc',function(req,res){ 71 | res.render('index',{datasets: datasets}); 72 | }); 73 | app.get('/api/doc/:moduleName', function(req,res){ 74 | res.render('module',{moduleName: req.params.moduleName}); 75 | }); 76 | 77 | app.get('/', function (req, res) { 78 | res.redirect('/api/doc/'); 79 | }); 80 | 81 | app.get('/api/', function (req, res) { 82 | res.redirect('/api/doc/'); 83 | }); 84 | /* ----------------------------------------------------------------------------- 85 | * Routes 86 | -----------------------------------------------------------------------------*/ 87 | /* Module cadastre */ 88 | app.use('/api/cadastre', cadastre); 89 | 90 | /* Module Limites Administratives */ 91 | app.use('/api/limites-administratives', limites_administratives); 92 | 93 | /* Module AOC */ 94 | app.use('/api/aoc',aoc); 95 | 96 | /* Module code postaux */ 97 | app.use('/api/codes-postaux', codes_postaux); 98 | 99 | /* Module GPU */ 100 | app.use('/api/gpu',gpu); 101 | 102 | /* Module RPG */ 103 | app.use('/api/rpg',rpg); 104 | 105 | /* Module Nature */ 106 | app.use('/api/nature',nature); 107 | 108 | /* Module all module IGN */ 109 | app.use('/api/wfs-geoportail',wfs_geoportail); 110 | 111 | /* Module Espace Revendeur */ 112 | app.use('/api/er',er); 113 | 114 | /* Module BDUni */ 115 | app.use('/api/bduni',bduni); 116 | 117 | /* Module Dreal Corse */ 118 | app.use('/api/corse/',corse); 119 | 120 | /* Endpoints dédié à la surveillance */ 121 | app.use('/api/health/',health); 122 | 123 | export {app}; 124 | -------------------------------------------------------------------------------- /checker/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | isCodeInsee: require('./isCodeInsee'), 3 | isGeometry: require('./isGeometry') 4 | }; 5 | -------------------------------------------------------------------------------- /checker/isCodeInsee.js: -------------------------------------------------------------------------------- 1 | import parseInseeCode from "../helper/parseInseeCode.js"; 2 | 3 | /** 4 | * Validation des codes insee 5 | * @param {String} value 6 | */ 7 | var isCodeInsee = function(value){ 8 | var inseeParts = parseInseeCode(value); 9 | return true; 10 | }; 11 | 12 | export default isCodeInsee; -------------------------------------------------------------------------------- /checker/isGeometry.js: -------------------------------------------------------------------------------- 1 | import { hint as geojsonhint } from "@mapbox/geojsonhint"; 2 | 3 | /** 4 | * Validation des géométries geojson 5 | * @param {Object} value 6 | */ 7 | var isGeometry = function(value){ 8 | var errors = geojsonhint(value).filter(function(error){ 9 | if ( typeof error.level !== 'undefined' ){ 10 | if ( error.level !== 'error' ){ 11 | return false; 12 | } 13 | } 14 | return true; 15 | }); 16 | if ( errors.length !== 0 ){ 17 | var message = errors.map(error => error.message).join(', '); 18 | throw new Error(message); 19 | } 20 | return true; 21 | }; 22 | 23 | export default isGeometry; 24 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4.2 4 | 5 | environment: 6 | GEOPORTAIL_REFERER: https://apicarto.sgmap.fr 7 | # GEOPORTAIL_KEY is defined in the CircleCI web UI 8 | # REF_DATA_DIR is defined in the CircleCI web UI 9 | 10 | dependencies: 11 | pre: 12 | - sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y 13 | - sudo apt-get update 14 | - sudo apt-get install -y gdal-bin 15 | post: 16 | - psql -c "CREATE DATABASE apicarto;" -U postgres 17 | - psql -d apicarto -c "CREATE EXTENSION postgis;" -U postgres 18 | - npm config set apicarto:pgUser postgres 19 | - npm config list 20 | - npm install coveralls 21 | 22 | test: 23 | post: 24 | - cat ./coverage/lcov.info | COVERALLS_REPO_TOKEN=jVgNlyHIaEgjaCRhvUYzPoYSi2y8eoao0 coveralls 25 | -------------------------------------------------------------------------------- /controllers/aoc/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import aocWfsClient from '../../middlewares/aocWfsClient.js'; 7 | import gppWfsClient from '../../middlewares/gppWfsClient.js'; 8 | import GeoJSONParser from 'jsts/org/locationtech/jts/io/GeoJSONParser.js'; 9 | import RelateOp from 'jsts/org/locationtech/jts/operation/relate/RelateOp.js'; 10 | import _ from 'lodash'; 11 | 12 | var router = new Router(); 13 | 14 | /** 15 | * Creation d'une chaîne de proxy sur le geoportail 16 | * @param {String} featureTypeName le nom de la couche WFS 17 | */ 18 | function createAocProxy(featureTypeName) { 19 | return [ 20 | gppWfsClient, 21 | aocWfsClient, 22 | validateParams, 23 | function(req,res){ 24 | var params = matchedData(req); 25 | //récupération des features 26 | getFeat(req, res, featureTypeName, params); 27 | } 28 | ]; 29 | }; 30 | 31 | var getFeat = function(req, res, featureTypeName, params) { 32 | 33 | req.aocWfsClient.headers.apikey = params.apikey; 34 | params = _.omit(params,'apikey'); 35 | 36 | req.gppWfsClient.getFeatures('ADMINEXPRESS-COG.LATEST:commune', params) 37 | .then(function(featureCollectionCommune) { 38 | let codeInsee = []; 39 | let geomCom = []; 40 | for(let i in featureCollectionCommune.features) { 41 | codeInsee.push(featureCollectionCommune.features[i].properties.insee_com); 42 | geomCom.push(featureCollectionCommune.features[i].geometry); 43 | } 44 | let geom = params.geom; 45 | params = _.omit(params,'geom'); 46 | params.insee = codeInsee; 47 | req.aocWfsClient.getFeatures(featureTypeName, params) 48 | .then(function(featureCollection){ 49 | for(let i in featureCollection.features) { 50 | 51 | featureCollection.features[i].properties.id_uni = featureCollection.features[i].properties.iduni; 52 | delete featureCollection.features[i].properties.iduni; 53 | 54 | featureCollection.features[i].properties.appellation = featureCollection.features[i].properties.appellatio; 55 | delete featureCollection.features[i].properties.appellatio; 56 | 57 | if(isFalseGeometry(featureCollection.features[i].geometry)) { 58 | featureCollection.features[i].geometry = null; 59 | delete featureCollection.features[i].bbox; 60 | } 61 | 62 | if(featureCollection.features[i].properties.segment == 1) { 63 | featureCollection.features[i].properties.granularite = 'exacte'; 64 | } else { 65 | featureCollection.features[i].properties.granularite = 'commune'; 66 | } 67 | 68 | if(featureCollection.features[i].properties.idapp == '1022') { 69 | featureCollection.features[i].properties.instruction_obligatoire = true; 70 | } else{ 71 | featureCollection.features[i].properties.instruction_obligatoire = false; 72 | } 73 | 74 | if(featureCollection.features[i].properties.granularite == 'commune' 75 | && featureCollection.features[i].properties.instruction_obligatoire == false) { 76 | for(let j in codeInsee) { 77 | if(featureCollection.features[i].properties.insee == codeInsee[j]) { 78 | featureCollection.features[i].geometry = geomCom[j]; 79 | break; 80 | } 81 | } 82 | } 83 | 84 | if(featureCollection.features[i].geometry) 85 | { 86 | featureCollection.features[i].properties.contains = isContained(geom, featureCollection.features[i].geometry); 87 | } else { 88 | featureCollection.features[i].properties.contains = null; 89 | } 90 | } 91 | res.json(featureCollection); 92 | }) 93 | .catch(function(err) { 94 | res.status(500).json(err); 95 | }) 96 | ; 97 | }); 98 | }; 99 | 100 | var isFalseGeometry = function(geom) { 101 | let isFalseGeom = false; 102 | if(geom.coordinates 103 | && geom.coordinates[0] 104 | && geom.coordinates[0][0] 105 | && geom.coordinates[0][0][0] 106 | && geom.coordinates[0][0][0][0] == 3 107 | && geom.coordinates[0][0][0][1] == 49) { 108 | 109 | isFalseGeom = true; 110 | } 111 | return isFalseGeom; 112 | }; 113 | 114 | var isContained = function(geom1, geom2) { 115 | 116 | let geoParser = new GeoJSONParser(); 117 | 118 | let jstsGeom1 = geoParser.read(geom1); 119 | let jstsGeom2 = geoParser.read(geom2); 120 | 121 | return RelateOp.contains(jstsGeom1, jstsGeom2); 122 | }; 123 | 124 | var corsOptionsGlobal = function(origin,callback) { 125 | var corsOptions; 126 | if (origin) { 127 | corsOptions = { 128 | origin: origin, 129 | optionsSuccessStatus: 200, 130 | methods: 'GET,POST', 131 | credentials: true 132 | }; 133 | } else { 134 | corsOptions = { 135 | origin : '*', 136 | optionsSuccessStatus : 200, 137 | methods: 'GET,POST', 138 | credentials: true 139 | }; 140 | } 141 | callback(null, corsOptions); 142 | }; 143 | 144 | /** 145 | * Permet d'alerter en cas de paramètre ayant changer de nom 146 | * 147 | * TODO Principe à valider (faire un middleware de renommage des paramètres screateCadastreProxyi l'approche est trop violente) 148 | */ 149 | var moduleValidator = [ 150 | check('geom').exists().custom(isGeometry), 151 | check('apikey').exists() 152 | ]; 153 | 154 | 155 | router.post('/appellation-viticole', cors(corsOptionsGlobal),moduleValidator, createAocProxy('appellation_inao_fam_gpkg_wfs:appellation_inao_fam')); 156 | 157 | export {router}; 158 | -------------------------------------------------------------------------------- /controllers/bduni/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import * as turf from '@turf/turf'; 4 | import { check, matchedData, oneOf } from 'express-validator'; 5 | import validateParams from '../../middlewares/validateParams.js'; 6 | import isGeometry from '../../checker/isGeometry.js'; 7 | import bduniWfsClient from '../../middlewares/bduniWfsClient.js'; 8 | import _ from 'lodash'; 9 | import NodeCache from 'node-cache'; 10 | 11 | const myCache = new NodeCache(); 12 | 13 | var router = new Router(); 14 | /** 15 | * Creation d'une chaîne de proxy sur le geoportail 16 | * @param {String} featureTypeName le nom de la couche WFS 17 | */ 18 | function createBduniProxy(featureTypeName){ 19 | return [ 20 | bduniWfsClient, 21 | validateParams, 22 | function(req,res){ 23 | var params = matchedData(req); 24 | 25 | /*Valeur par défaut du paramètre distance*/ 26 | if ( typeof params.distance == 'undefined' ) { 27 | params.distance = 100;} 28 | 29 | /* Value default pour _limit an _start */ 30 | if ( typeof params._start == 'undefined' ) { params._start = 0;} 31 | if( typeof params._limit == 'undefined') {params._limit = 1000;} 32 | 33 | if(params.lon && params.lat) { 34 | params.geom = '{"type": "Point","coordinates":[' + params.lon + ',' + params.lat + ']}'; 35 | } 36 | params = _.omit(params,'lon'); 37 | params = _.omit(params,'lat'); 38 | 39 | /* requête WFS GPP*/ 40 | req.bduniWfsClient.getFeatures(featureTypeName, params) 41 | .then(function(featureCollection) { 42 | featureCollection = format(featureCollection, params.geom); 43 | getDepartmentName(req, res, featureCollection); 44 | }) 45 | .catch(function(err) { 46 | res.status(500).json(err); 47 | }); 48 | } 49 | ]; 50 | } 51 | 52 | var format = function(featureCollection, geom) { 53 | let features = featureCollection.features; 54 | let formated_features = []; 55 | 56 | for(let i in features) { 57 | let neo_feat = new Object(); 58 | neo_feat.type = features[i].type; 59 | neo_feat.id = features[i].id; 60 | neo_feat.geometry_name = features[i].geometry_name; 61 | neo_feat.properties = { 62 | cleabs : features[i].properties.cleabs, 63 | cl_admin : features[i].properties.cpx_classement_administratif, 64 | nature : features[i].properties.nature, 65 | pos_sol : features[i].properties.position_par_rapport_au_sol, 66 | importance : features[i].properties.importance, 67 | nb_voies : features[i].properties.nombre_de_voies, 68 | sens : features[i].properties.sens_de_circulation, 69 | largeur : features[i].properties.largeur_de_chaussee, 70 | gestion : features[i].properties.cpx_gestionnaire, 71 | numero : features[i].properties.cpx_numero 72 | }; 73 | if(neo_feat.properties.nb_voies) { 74 | neo_feat.properties.nb_voies = neo_feat.properties.nb_voies.toString(); 75 | } 76 | 77 | let nearestPoint = getNearestPoint(features[i].geometry.coordinates, JSON.parse(geom).coordinates); 78 | neo_feat.geometry = nearestPoint.geometry; 79 | if(neo_feat.geometry.coordinates.length > 2) { 80 | neo_feat.geometry.coordinates.pop(); 81 | } 82 | neo_feat.properties.distance = nearestPoint.properties.dist; 83 | 84 | formated_features.push(neo_feat); 85 | } 86 | featureCollection.features = formated_features; 87 | return featureCollection; 88 | }; 89 | 90 | var getNearestPoint = function(line, point) { 91 | return turf.nearestPointOnLine(turf.lineString(line), turf.point(point),{units: 'meters'}); 92 | }; 93 | 94 | var getDepartmentName = function(req, res, featureCollection) { 95 | let setDepName = function(featureCollection, depList) { 96 | for(let i in featureCollection.features) { 97 | for(let j in depList) { 98 | if(featureCollection.features[i].properties.gestion == depList[j].nom) { 99 | featureCollection.features[i].properties.gestion = depList[j].insee_dep; 100 | break; 101 | } 102 | } 103 | } 104 | return featureCollection; 105 | }; 106 | 107 | if(myCache.get('departmentsList')) { 108 | featureCollection = setDepName(featureCollection, myCache.get('departmentsList')); 109 | return res.json(featureCollection); 110 | } else { 111 | req.bduniWfsClient.getFeatures('ADMINEXPRESS-COG-CARTO.LATEST:departement', {'_propertyNames': ['insee_dep', 'nom']}) 112 | .then(function(featureCollectionDep) { 113 | let list = []; 114 | for(let i in featureCollectionDep.features) { 115 | let feat = featureCollectionDep.features[i]; 116 | list.push({'nom' : feat.properties.nom, 'insee_dep' : feat.properties.insee_dep}); 117 | } 118 | featureCollection = setDepName(featureCollection, list); 119 | myCache.set('departmentsList', list); 120 | return res.json(featureCollection); 121 | }) 122 | .catch(function(err) { 123 | res.status(500).json(err); 124 | }); 125 | } 126 | }; 127 | 128 | var corsOptionsGlobal = function(origin,callback) { 129 | var corsOptions; 130 | if (origin) { 131 | corsOptions = { 132 | origin: origin, 133 | optionsSuccessStatus: 200, 134 | methods: 'GET,POST', 135 | credentials: true 136 | }; 137 | } else { 138 | corsOptions = { 139 | origin : '*', 140 | optionsSuccessStatus : 200, 141 | methods: 'GET,POST', 142 | credentials: true 143 | }; 144 | } 145 | callback(null, corsOptions); 146 | }; 147 | 148 | var isPoint = function(geom) { 149 | if(JSON.parse(geom).type == 'Point' && JSON.parse(geom).coordinates.length == 2) { 150 | return true; 151 | } else { 152 | return false; 153 | } 154 | }; 155 | 156 | /** 157 | * Permet d'alerter en cas de paramètre ayant changer de nom 158 | * 159 | * TODO Principe à valider (faire un middleware de renommage des paramètres si l'approche est trop violente) 160 | */ 161 | 162 | 163 | var moduleValidators = [ 164 | oneOf( 165 | [ 166 | check('geom').exists(), 167 | [check('lon').exists(), check('lat').exists()] 168 | ], {message: 'Il faut renseigner soit le champ "geom", soit les champs "lon" et "lat".'} 169 | ), 170 | oneOf( 171 | [ 172 | check('geom').isEmpty(), 173 | [check('lon').isEmpty(), check('lat').isEmpty()] 174 | ], {message: 'Si le champ "geom" est renseigné, les champs "lon" et "lat" ne doivent pas être renseignés.'} 175 | ), 176 | check('geom').optional().custom(isGeometry).withMessage('La géométrie est invalide.'), 177 | check('geom').optional().custom(isPoint).withMessage('La géométrie doit être un Point.'), 178 | check('_limit').optional().isNumeric().withMessage('Le champ "_limit" doit être un entier'), 179 | check('_start').optional().isNumeric().withMessage('Le champ "_start" doit être un entier'), 180 | check('lon').optional().isNumeric().withMessage('La longitude est invalide'), 181 | check('lat').optional().isNumeric().withMessage('La latitude est invalide'), 182 | check('distance').optional().isFloat({min: 0}).withMessage('La distance doit être un nombre positif') 183 | ]; 184 | 185 | /** 186 | * Récupération des couches 187 | * 188 | */ 189 | 190 | router.get('/troncon', cors(corsOptionsGlobal),moduleValidators, createBduniProxy('BDTOPO_V3:troncon_de_route')); 191 | router.post('/troncon', cors(corsOptionsGlobal),moduleValidators, createBduniProxy('BDTOPO_V3:troncon_de_route')); 192 | 193 | export {router}; 194 | -------------------------------------------------------------------------------- /controllers/cadastre/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import isCodeInsee from '../../checker/isCodeInsee.js'; 7 | import parseInseeCode from '../../helper/parseInseeCode.js'; 8 | import gppWfsClient from '../../middlewares/gppWfsClient.js'; 9 | import _ from 'lodash'; 10 | 11 | var router = new Router(); 12 | 13 | /** 14 | * Creation d'une chaîne de proxy sur le geoportail 15 | * @param {String} featureTypeName le nom de la couche WFS 16 | */ 17 | function createCadastreProxy(featureTypeName){ 18 | return [ 19 | gppWfsClient, 20 | validateParams, 21 | function(req,res){ 22 | var params = matchedData(req); 23 | var featureTypeNameFinal = featureTypeName; 24 | params = _.omit(params,'apikey'); 25 | if ((params.source_ign) && (featureTypeName != 'BDPARCELLAIRE-VECTEUR_WLD_BDD_WGS84G:divcad') && (featureTypeName != 'CADASTRALPARCELS.PARCELLAIRE_EXPRESS:feuille')) { 26 | if(params.source_ign.toUpperCase() == 'BDP') { 27 | featureTypeNameFinal = featureTypeName.replace('CADASTRALPARCELS.PARCELLAIRE_EXPRESS','BDPARCELLAIRE-VECTEUR_WLD_BDD_WGS84G'); 28 | } else if(params.source_ign.toUpperCase() == 'PCI') { 29 | featureTypeNameFinal = featureTypeName; 30 | } else { 31 | return res.status(400).send({ 32 | code: 400, 33 | message: 'Pour une recherche sur la couche PCI EXPRESS : la valeur doit être PCI.Pour une recherche sur la couche BD Parcellaire: la valeur doit être BDP.' 34 | }); 35 | 36 | } 37 | } 38 | params = _.omit(params,'source_ign'); 39 | 40 | /* insee => code_dep et code_com */ 41 | if ( params.code_insee ){ 42 | var inseeParts = parseInseeCode(params.code_insee); 43 | params.code_dep = inseeParts.code_dep; 44 | params.code_com = inseeParts.code_com; 45 | params = _.omit(params,'code_insee'); 46 | } 47 | 48 | /* hack du couple code_dep et code_com + limite réponse à 500 features dans le cas des communes */ 49 | if ( featureTypeNameFinal.endsWith('commune') ){ 50 | if ( params.code_dep && params.code_com ){ 51 | params.code_insee = params.code_dep + params.code_com ; 52 | params = _.omit(params,'code_com'); 53 | params = _.omit(params,'code_dep'); 54 | } 55 | if( typeof params._limit == 'undefined') {params._limit = 500;} 56 | if( params._limit > 500) { 57 | return res.status(400).send({ 58 | code: 400, 59 | message: 'La valeur de l\'attribut "limit" doit être inférieur ou égal à 500.' 60 | }); 61 | } 62 | } 63 | 64 | /* Value default pour _limit an _start */ 65 | if ( typeof params._start == 'undefined' ) {params._start = 0;} 66 | if( typeof params._limit == 'undefined') {params._limit = 1000;} 67 | 68 | /* requête WFS GPP*/ 69 | req.gppWfsClient.getFeatures(featureTypeNameFinal, params) 70 | /* uniformisation des attributs en sortie */ 71 | .then(function(featureCollection){ 72 | featureCollection.features.forEach(function(feature){ 73 | if ( ! feature.properties.code_insee ){ 74 | feature.properties.code_insee = feature.properties.code_dep+feature.properties.code_com; 75 | } 76 | }); 77 | if(featureCollection.links && featureCollection.links.length) { 78 | for(let i in featureCollection.links) { 79 | if(featureCollection.links[i].href && featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)) { 80 | let num = featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)[0].replace('STARTINDEX=',''); 81 | let href = req.gppWfsClient.headers.Referer.replace(/\/api.*/, '') + req.originalUrl; 82 | if(href.match('_start')) { 83 | href = href.replace(/_start\=[0-9]*/, '_start=' + num); 84 | } else { 85 | href += '&_start=' + num; 86 | } 87 | featureCollection.links[i].href = href; 88 | } 89 | } 90 | } 91 | return featureCollection; 92 | }) 93 | .then(function(featureCollection) { 94 | res.json(featureCollection); 95 | }) 96 | .catch(function(err) { 97 | res.status(500).json(err); 98 | }); 99 | } 100 | ]; 101 | } 102 | 103 | 104 | var corsOptionsGlobal = function(origin,callback) { 105 | var corsOptions; 106 | if (origin) { 107 | corsOptions = { 108 | origin: origin, 109 | optionsSuccessStatus: 200, 110 | methods: 'GET,POST', 111 | credentials: true 112 | }; 113 | } else { 114 | corsOptions = { 115 | origin : '*', 116 | optionsSuccessStatus : 200, 117 | methods: 'GET,POST', 118 | credentials: true 119 | }; 120 | } 121 | callback(null, corsOptions); 122 | }; 123 | 124 | /**1000 125 | * Permet d'alerter en cas de paramètre ayant changer de nom 126 | * 127 | * TODO Principe à valider (faire un middleware de renommage des paramètres si l'approche est trop violente) 128 | */ 129 | 130 | 131 | var legacyValidators = [ 132 | check('codearr').optional().custom(function(){return false;}).withMessage('Le paramètre "codearr" a été remplacé par "code_arr" pour éviter des renommages dans les données et chaînage de requête'), 133 | check('dep').optional().custom(function(){return false;}).withMessage('Le paramètre "dep" a été remplacé par "code_dep" pour éviter des renommages dans les données et chaînage de requête'), 134 | check('insee').optional().custom(function(){return false;}).withMessage('Le paramètre "insee" a été remplacé par "code_insee" pour éviter des renommages dans les données et chaînage de requête'), 135 | check('source_ign').optional().isString().isLength({min:3,max:3}).withMessage('Les seules valeurs possibles sont: PCI pour utiliser les couches PCI-Express ou BDP pour utiliser les couches BD Parcellaires') 136 | ]; 137 | 138 | var communeValidators = legacyValidators.concat([ 139 | check('code_insee').optional().custom(isCodeInsee), 140 | check('code_dep').optional().isAlphanumeric().isLength({min:2,max:2}).withMessage('Code département invalide'), 141 | check('code_com').optional().isNumeric().isLength({min:2,max:3}).withMessage('Code commune invalide'), 142 | check('nom_com').optional(), 143 | check('geom').optional().custom(isGeometry), 144 | check('_limit').optional().isNumeric(), 145 | check('_start').optional().isNumeric() 146 | ]); 147 | 148 | router.get('/commune', cors(corsOptionsGlobal),communeValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:commune')); 149 | router.post('/commune',cors(corsOptionsGlobal),communeValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:commune')); 150 | 151 | /** 152 | * Récupération des divisions de la BDParcellaire 153 | * la valeur source_ign ne sera pas utilisée pour la recherche. 154 | * Nous avons la requête module pour faire directement une recherche sur PCI EXPRESS 155 | */ 156 | 157 | var divisionValidators = communeValidators.concat([ 158 | check('section').optional().isAlphanumeric().isLength({min:2,max:2}).withMessage('Le numéro de section est sur 2 caractères'), 159 | check('code_arr').optional().isNumeric().isLength({min:3,max:3}).withMessage('Le code arrondissement est composé de 3 chiffres'), 160 | check('com_abs').optional().isNumeric().isLength({min:3,max:3}).withMessage('Le prefixe est composé de 3 chiffres obligatoires') 161 | ]); 162 | router.get('/division', cors(corsOptionsGlobal),divisionValidators, createCadastreProxy('BDPARCELLAIRE-VECTEUR_WLD_BDD_WGS84G:divcad')); 163 | router.post('/division', cors(corsOptionsGlobal),divisionValidators, createCadastreProxy('BDPARCELLAIRE-VECTEUR_WLD_BDD_WGS84G:divcad')); 164 | 165 | 166 | /** 167 | * Récupération des parcelles pour une commune. 168 | * 169 | * Paramètres : code_dep=25 et code_com=349 170 | * 171 | */ 172 | var parcelleValidators = divisionValidators.concat([ 173 | check('numero').optional().matches(/\w{4}/).withMessage('Le numéro de parcelle est sur 4 caractères') 174 | ]); 175 | router.get('/parcelle', cors(corsOptionsGlobal),parcelleValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:parcelle')); 176 | router.post('/parcelle', cors(corsOptionsGlobal),parcelleValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:parcelle')); 177 | 178 | /** 179 | * Récupération des localisants 180 | * 181 | * Paramètres : une feature avec pour nom "geom"... 182 | * 183 | */ 184 | router.get('/localisant',cors(corsOptionsGlobal),parcelleValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:localisant')); 185 | router.post('/localisant', cors(corsOptionsGlobal),parcelleValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:localisant')); 186 | 187 | 188 | /** 189 | * Récupérations des feuilles(divisions sur BDParcellaire) pour PCI Express 190 | * Les champs validator sont identiques aux divisions 191 | * 192 | */ 193 | 194 | router.get('/feuille', cors(corsOptionsGlobal),divisionValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:feuille')); 195 | router.post('/feuille', cors(corsOptionsGlobal),divisionValidators, createCadastreProxy('CADASTRALPARCELS.PARCELLAIRE_EXPRESS:feuille')); 196 | 197 | 198 | export { router }; 199 | -------------------------------------------------------------------------------- /controllers/codes-postaux/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { check, matchedData } from 'express-validator'; 3 | import validateParams from '../../middlewares/validateParams.js'; 4 | import codesPostaux from 'codes-postaux'; 5 | 6 | var router = new Router(); 7 | 8 | router.get('/communes/:codePostal', [ 9 | check('codePostal').matches(/^\d{5}$/).withMessage('Code postal invalide') 10 | ], validateParams, function (req, res) { 11 | const filter = matchedData(req); 12 | var result = codesPostaux.find(filter.codePostal); 13 | if (! result.length){ 14 | return res.sendStatus(404); 15 | } 16 | res.json(result); 17 | }); 18 | 19 | export {router}; 20 | -------------------------------------------------------------------------------- /controllers/corse/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import drealCorseWfsClient from '../../middlewares/drealCorseWfsClient.js'; 7 | import _ from 'lodash'; 8 | 9 | 10 | var router = new Router(); 11 | 12 | /** 13 | * Creation d'une chaîne de proxy sur le geoportail 14 | * @param {String} featureTypeName le nom de la couche WFS 15 | */ 16 | function createCorseProxy(featureTypeName){ 17 | return [ 18 | drealCorseWfsClient, 19 | validateParams, 20 | function(req,res){ 21 | var params = matchedData(req); 22 | /* requête WFS Flux Corse*/ 23 | req.drealCorseWfsClient.getFeatures(featureTypeName, params) 24 | .then(function(featureCollection) { 25 | res.json(featureCollection); 26 | }) 27 | .catch(function(err) { 28 | res.status(500).json(err); 29 | }); 30 | } 31 | ]; 32 | } 33 | 34 | function createAllCorseProxy(){ 35 | return [ 36 | drealCorseWfsClient, 37 | validateParams, 38 | function(req,res){ 39 | var params = matchedData(req); 40 | var featureTypeName= params.source; 41 | params = _.omit(params,'source'); 42 | /* requête WFS Flux Corse*/ 43 | req.drealCorseWfsClient.getFeatures(featureTypeName, params) 44 | .then(function(featureCollection) { 45 | res.json(featureCollection); 46 | }) 47 | .catch(function(err) { 48 | res.status(500).json(err); 49 | }); 50 | } 51 | ]; 52 | } 53 | 54 | 55 | var corsOptionsGlobal = function(origin,callback) { 56 | var corsOptions; 57 | if (origin) { 58 | corsOptions = { 59 | origin: origin, 60 | optionsSuccessStatus: 200, 61 | methods: 'GET,POST', 62 | credentials: true 63 | }; 64 | } else { 65 | corsOptions = { 66 | origin : '*', 67 | optionsSuccessStatus : 200, 68 | methods: 'GET,POST', 69 | credentials: true 70 | }; 71 | } 72 | callback(null, corsOptions); 73 | }; 74 | 75 | /** 76 | * Permet d'alerter en cas de paramètre ayant changer de nom 77 | * 78 | * TODO Principe à valider (faire un middleware de renommage des paramètres si l'approche est trop violente) 79 | */ 80 | 81 | 82 | var natureValidators = [ 83 | check('geom').optional().custom(isGeometry) 84 | ]; 85 | 86 | 87 | var corseForetValidators = natureValidators.concat([ 88 | check('ccod_frt').optional().isString(), 89 | check('llib_frt').optional().isString(), 90 | check('propriete').optional().isString(), 91 | check('s_sig_ha').optional().isString(), 92 | check('dpt').optional().isString(), 93 | check('nom_fore').optional().isString() 94 | 95 | ]); 96 | 97 | router.get('/foretcorse',cors(corsOptionsGlobal),corseForetValidators, createCorseProxy('dreal:fsoum_25')); 98 | router.post('/foretcorse',cors(corsOptionsGlobal),corseForetValidators, createCorseProxy('dreal:fsoum_25')); 99 | 100 | /** 101 | *Recherche flux geoorchectra corse pour les Forêts bénéficiant du régime forestier 102 | * 103 | */ 104 | 105 | var corsePecheValidators = natureValidators.concat([ 106 | check('dpt').optional().isString() 107 | 108 | ]); 109 | 110 | router.get('/pechecorse',cors(corsOptionsGlobal),corsePecheValidators, createCorseProxy('dreal:res_pech25')); 111 | router.post('/pechecorse',cors(corsOptionsGlobal),corsePecheValidators, createCorseProxy('dreal:res_pech25')); 112 | 113 | /** 114 | * Accès à toutes les couches geochestra.ac-corse.fr 115 | */ 116 | 117 | var moduleValidator = [ 118 | check('source').exists().withMessage('Le paramètre source pour le nom de la couche est obligatoire'), 119 | check('geom').optional().custom(isGeometry), 120 | 121 | ]; 122 | 123 | router.get('/search', cors(corsOptionsGlobal),moduleValidator, createAllCorseProxy()); 124 | router.post('/search', cors(corsOptionsGlobal),moduleValidator, createAllCorseProxy()); 125 | 126 | export {router}; 127 | -------------------------------------------------------------------------------- /controllers/er/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import erWfsClient from '../../middlewares/erWfsClient.js'; 7 | import _ from 'lodash'; 8 | 9 | 10 | var router = new Router(); 11 | 12 | /** 13 | * Creation d'une chaîne de proxy sur le geoportail 14 | * @param {String} featureTypeName le nom de la couche WFS 15 | */ 16 | function createErProxy(featureTypeName,typeSearch){ 17 | return [ 18 | erWfsClient, 19 | validateParams, 20 | function(req,res){ 21 | var params = matchedData(req); 22 | 23 | /** Gestion affichage des valeurs avec has_geometrie 24 | * si true : affichage uniquement des résultats avec géométrie 25 | * si false: affichage des résultats avec ou sans géométrie 26 | */ 27 | 28 | if((typeSearch == 'product') || (typeSearch == 'category')) { 29 | if(params.admin == 'Y') { 30 | params.has_geometry=false; 31 | } else { 32 | params.has_geometry=true; 33 | } 34 | } 35 | 36 | params = _.omit(params,'admin'); 37 | 38 | /** Gestion de la requete product */ 39 | if (typeSearch == 'product') { 40 | //For module Product utilisation parametre name pour la recherche 41 | if (params.name) { 42 | params.namepr = params.name.toUpperCase(); 43 | params = _.omit(params,'name'); 44 | } 45 | if((params.date_maj_deb) && (params.date_maj_fin)) { 46 | params.field_date = params.date_maj_deb +'T00:00:00Z;'+ params.date_maj_fin+'T23:59:59Z'; 47 | params = _.omit(params,'date_maj_deb'); 48 | params = _.omit(params,'date_maj_fin'); 49 | } else { 50 | if((params.date_maj_deb) || (params.date_maj_fin)) { 51 | return res.status(400).send({ 52 | code: 400, 53 | message: 'Utilisation des dates avec une date de fin et une date de debut avec moins de 6 mois entre les 2 dates.' 54 | }); 55 | } 56 | } 57 | 58 | // For params publication_date select between 2 dates 59 | 60 | if((params.publi_date_deb) && (params.publi_date_fin)) { 61 | params.field_publication_date = params.publi_date_deb +'T00:00:00Z;'+ params.publi_date_fin+'T23:59:59Z'; 62 | params = _.omit(params,'publi_date_deb'); 63 | params = _.omit(params,'publi_date_fin'); 64 | } else { 65 | if((params.publi_date_deb) || (params.publi_date_fin)) { 66 | return res.status(400).send({ 67 | code: 400, 68 | message: 'Utilisation des dates de publication avec une date de fin et une date de debut avec moins de 6 mois entre les 2 dates.' 69 | }); 70 | } 71 | } 72 | 73 | // For params depublication_date select between 2 dates 74 | 75 | if((params.depubli_date_deb) && (params.depubli_date_fin)) { 76 | params.field_depublication_date = params.depubli_date_deb +'T00:00:00Z;'+ params.depubli_date_fin+'T23:59:59Z'; 77 | params = _.omit(params,'depubli_date_deb'); 78 | params = _.omit(params,'depubli_date_fin'); 79 | } else { 80 | if((params.depubli_date_deb) || (params.depubli_date_fin)) { 81 | return res.status(400).send({ 82 | code: 400, 83 | message: 'Utilisation des dates de depublication avec une date de fin et une date de debut avec moins de 6 mois entre les 2 dates.' 84 | }); 85 | } 86 | } 87 | } 88 | 89 | //For _propertyNames, we need to transform the string in Array 90 | if(params._propertyNames) { 91 | params._propertyNames = params._propertyNames.split(';'); 92 | } 93 | // For module Category Gestion du parametre name 94 | if (typeSearch == 'category') { 95 | if (params.name && params.type) { 96 | if(params.type == 's') { 97 | // Recherche sur segment_title 98 | params.segment_title = params.name; 99 | } else if (params.type == 't') { 100 | //Recherche sur theme_title 101 | params.theme_title = params.name; 102 | }else if (params.type == 'c') { 103 | // Recherche sur collection_title 104 | params.collection_title = params.name; 105 | } else { 106 | return res.status(400).send({ 107 | code: 400, 108 | message: 'Le champ type contient uniquement les valeurs t, c ou s' 109 | }); 110 | } 111 | /* Suppression des paramètres après transformations */ 112 | params = _.omit(params,'name'); 113 | params = _.omit(params,'type'); 114 | } else { 115 | if(params.name || params.type) { 116 | return res.status(400).send({ 117 | code: 400, 118 | message: 'Les 2 champs name et type doivent être renseignés pour cette recherche spécfique. Pour Le champ type les valeurs acceptées sont t,c ou s' 119 | }); 120 | } 121 | } 122 | } 123 | 124 | /** Gestion de la requete Grid */ 125 | if (typeSearch == 'grid') { 126 | if (params.title) { 127 | params.title = params.title.toUpperCase(); 128 | } 129 | if (params.zip_codes) { 130 | params.zip_codes = '["'+ params.zip_codes.replace(',' , '","') + '"]'; 131 | } 132 | } 133 | /* Value default pour _limit an _start */ 134 | if ( typeof params._start == 'undefined' ) {params._start = 0;} 135 | if( typeof params._limit == 'undefined') {params._limit = 1000;} 136 | 137 | /* requête WFS GPP*/ 138 | req.erWfsClient.getFeatures(featureTypeName, params) 139 | .then(function(featureCollection) { 140 | res.json(featureCollection); 141 | }) 142 | .catch(function(err) { 143 | res.status(500).json(err); 144 | }); 145 | } 146 | ]; 147 | } 148 | 149 | var corsOptionsGlobal = function(origin,callback) { 150 | var corsOptions; 151 | if (origin) { 152 | corsOptions = { 153 | origin: origin, 154 | optionsSuccessStatus: 200, 155 | methods: 'GET,POST', 156 | credentials: true 157 | }; 158 | } else { 159 | corsOptions = { 160 | origin : '*', 161 | optionsSuccessStatus : 200, 162 | methods: 'GET,POST', 163 | credentials: true 164 | }; 165 | } 166 | callback(null, corsOptions); 167 | }; 168 | 169 | /** 170 | * Récuperation des produits de l'espace revendeur 171 | * 172 | */ 173 | 174 | 175 | var erValidators = [ 176 | check('geom').optional().custom(isGeometry), 177 | check('_limit').optional().isNumeric(), 178 | check('_start').optional().isNumeric(), 179 | check('_propertyNames').optional().isString() 180 | ]; 181 | 182 | var productValidators = erValidators.concat([ 183 | check('is_manufactured').optional().isBoolean(), 184 | check('code_ean').optional().isAlphanumeric(), 185 | check('code_article').optional().isString(), 186 | check('name').optional().isString(), 187 | check('type').optional().isString(), 188 | check('publication_date').optional().isString(), 189 | check('date_maj_deb').optional().isString(), // Param ne servant que pour admin 190 | check('date_maj_fin').optional().isString(), // Param ne servant que pour admin 191 | check('admin').optional().isAlphanumeric().isLength({min:1,max:1}).withMessage('Le champ admin doit être Y ou N'), 192 | check('publi_date_deb').optional().isString(), // Param ne servant que pour admin 193 | check('publi_date_fin').optional().isString(), // Param ne servant que pour admin 194 | check('depubli_date_deb').optional().isString(), // Param ne servant que pour admin 195 | check('depubli_date_fin').optional().isString() // Param ne servant que pour admin 196 | 197 | ]); 198 | 199 | router.get('/product', cors(corsOptionsGlobal),productValidators, createErProxy('espace_revendeurs:product','product')); 200 | router.post('/product',cors(corsOptionsGlobal),productValidators, createErProxy('espace_revendeurs:product','product')); 201 | 202 | /** 203 | * Récupération des information sur les category dans le flux product_view 204 | * 205 | */ 206 | 207 | var categoryValidators = erValidators.concat([ 208 | check('name').optional().isString(), 209 | check('type').optional().isAlphanumeric().isLength({min:1,max:1}).withMessage('Le type est sur 1 caractère'), 210 | check('category_id').optional().isString(), 211 | check('admin').optional().isAlphanumeric().isLength({min:1,max:1}).withMessage('Le champ admin doit être Y ou N') 212 | ]); 213 | 214 | router.get('/category', cors(corsOptionsGlobal),categoryValidators, createErProxy('espace_revendeurs:product' ,'category')); 215 | router.post('/category', cors(corsOptionsGlobal),categoryValidators, createErProxy('espace_revendeurs:product','category')); 216 | 217 | 218 | /** 219 | * Récuperation des informations sur le flux espace_revendeurs:grid_view 220 | * 221 | */ 222 | 223 | var gridValidators = erValidators.concat([ 224 | check('name').optional().isString(), 225 | check('title').optional().isString(), 226 | check('type').optional().isString(), 227 | check('zip_codes').optional().isString() 228 | ]); 229 | 230 | router.get('/grid', cors(corsOptionsGlobal),gridValidators, createErProxy('espace_revendeurs:grid','grid')); 231 | router.post('/grid', cors(corsOptionsGlobal),gridValidators, createErProxy('espace_revendeurs:grid','grid')); 232 | 233 | 234 | export {router}; -------------------------------------------------------------------------------- /controllers/gpu/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import isCodeInsee from '../../checker/isCodeInsee.js'; 7 | import gpuWfsClient from '../../middlewares/gpuWfsClient.js'; 8 | import _ from 'lodash'; 9 | 10 | var router = new Router(); 11 | 12 | /** 13 | * Creation d'une chaîne de proxy sur le GPU 14 | * @param {Object} typeName 15 | */ 16 | function createGpuProxy(typeName){ 17 | return [ 18 | validateParams, 19 | gpuWfsClient, 20 | function(req,res){ 21 | var params = matchedData(req); 22 | params._limit = 5000; 23 | 24 | //Si couche du type generateur ou assiette le champ categorie corresponds à suptype 25 | if (params.categorie) { 26 | if ((typeName.indexOf('generateur')) || (typeName.indexOf('assiette'))) { 27 | params.suptype = params.categorie.toLowerCase(); 28 | params = _.omit(params,'categorie'); 29 | } 30 | } 31 | 32 | req.gpuWfsClient.getFeatures(typeName, params) 33 | .then(function(featureCollection) { 34 | res.json(featureCollection); 35 | }) 36 | 37 | .catch(function(err) { 38 | res.status(500).json(err); 39 | }) 40 | ; 41 | } 42 | ]; 43 | } 44 | 45 | /*-------------------------------------------------------------------------------------------- 46 | * DU 47 | -------------------------------------------------------------------------------------------*/ 48 | 49 | const mapping = { 50 | 'municipality': 'wfs_du:municipality', 51 | 'document': 'wfs_du:document', 52 | 'zone-urba': 'wfs_du:zone_urba', 53 | 'secteur-cc': 'wfs_du:secteur_cc', 54 | 'prescription-pct': 'wfs_du:prescription_pct', 55 | 'prescription-lin': 'wfs_du:prescription_lin', 56 | 'prescription-surf': 'wfs_du:prescription_surf', 57 | 'info-pct': 'wfs_du:info_pct', 58 | 'info-surf': 'wfs_du:info_surf', 59 | 'info-lin': 'wfs_du:info_lin', 60 | 'acte-sup': 'wfs_sup:acte_sup', 61 | 'assiette-sup-p': 'wfs_sup:assiette_sup_p', 62 | 'assiette-sup-l': 'wfs_sup:assiette_sup_l', 63 | 'assiette-sup-s': 'wfs_sup:assiette_sup_s', 64 | 'generateur-sup-p': 'wfs_sup:generateur_sup_p', 65 | 'generateur-sup-l': 'wfs_sup:generateur_sup_l', 66 | 'generateur-sup-s': 'wfs_sup:generateur_sup_s' 67 | 68 | }; 69 | 70 | 71 | var corsOptionsGlobal = function(origin,callback) { 72 | var corsOptions; 73 | if (origin) { 74 | corsOptions = { 75 | origin: origin, 76 | optionsSuccessStatus: 200, 77 | methods: 'GET,POST', 78 | credentials: true 79 | }; 80 | } else { 81 | corsOptions = { 82 | origin : '*', 83 | optionsSuccessStatus : 200, 84 | methods: 'GET,POST', 85 | credentials: true 86 | }; 87 | } 88 | callback(null, corsOptions); 89 | }; 90 | /* Définition des tests paramètres */ 91 | var legacyValidators = [ 92 | check('geom').optional().custom(isGeometry) 93 | ]; 94 | 95 | var communeValidators = legacyValidators.concat([ 96 | check('insee').optional().custom(isCodeInsee) 97 | ]); 98 | 99 | var partitionValidators = legacyValidators.concat([ 100 | check('partition').optional().isString() 101 | ]); 102 | 103 | var categoriesValidators = partitionValidators.concat([ 104 | check('categorie').optional().isString() 105 | ]); 106 | 107 | router.get('/municipality', cors(corsOptionsGlobal),communeValidators, createGpuProxy(mapping['municipality'])); 108 | router.post('/municipality',cors(corsOptionsGlobal), communeValidators, createGpuProxy(mapping['municipality'])); 109 | 110 | 111 | router.get('/document',cors(corsOptionsGlobal),partitionValidators,createGpuProxy(mapping['document'])); 112 | router.post('/document',cors(corsOptionsGlobal),partitionValidators,createGpuProxy(mapping['document'])); 113 | 114 | router.get('/zone-urba', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['zone-urba'])); 115 | router.post('/zone-urba', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['zone-urba'])); 116 | 117 | router.get('/secteur-cc', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['secteur-cc'])); 118 | router.post('/secteur-cc', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['secteur-cc'])); 119 | 120 | router.get('/prescription-pct', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['prescription-pct'])); 121 | router.post('/prescription-pct', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['prescription-pct'])); 122 | 123 | router.get('/prescription-lin', cors(corsOptionsGlobal),partitionValidators,createGpuProxy(mapping['prescription-lin'])); 124 | router.post('/prescription-lin', cors(corsOptionsGlobal),partitionValidators,createGpuProxy(mapping['prescription-lin'])); 125 | 126 | router.get('/prescription-surf', cors(corsOptionsGlobal),partitionValidators ,createGpuProxy(mapping['prescription-surf'])); 127 | router.post('/prescription-surf', cors(corsOptionsGlobal),partitionValidators ,createGpuProxy(mapping['prescription-surf'])); 128 | 129 | router.get('/info-pct', cors(corsOptionsGlobal), partitionValidators,createGpuProxy(mapping['info-pct'])); 130 | router.post('/info-pct', cors(corsOptionsGlobal), partitionValidators,createGpuProxy(mapping['info-pct'])); 131 | 132 | router.get('/info-lin', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['info-lin'])); 133 | router.post('/info-lin', cors(corsOptionsGlobal),partitionValidators, createGpuProxy(mapping['info-lin'])); 134 | 135 | router.get('/info-surf', cors(corsOptionsGlobal), partitionValidators, createGpuProxy(mapping['info-surf'])); 136 | router.post('/info-surf', cors(corsOptionsGlobal), partitionValidators, createGpuProxy(mapping['info-surf'])); 137 | 138 | /*-------------------------------------------------------------------------------------------- 139 | * SUP 140 | -------------------------------------------------------------------------------------------*/ 141 | 142 | router.get('/acte-sup', cors(corsOptionsGlobal),partitionValidators,createGpuProxy(mapping['acte-sup'])); 143 | router.post('/acte-sup', cors(corsOptionsGlobal),partitionValidators,createGpuProxy(mapping['acte-sup'])); 144 | 145 | router.get('/assiette-sup-p', cors(corsOptionsGlobal),categoriesValidators , createGpuProxy(mapping['assiette-sup-p'])); 146 | router.post('/assiette-sup-p', cors(corsOptionsGlobal),categoriesValidators , createGpuProxy(mapping['assiette-sup-p'])); 147 | 148 | router.get('/assiette-sup-l', cors(corsOptionsGlobal),categoriesValidators , createGpuProxy(mapping['assiette-sup-l'])); 149 | router.post('/assiette-sup-l', cors(corsOptionsGlobal),categoriesValidators , createGpuProxy(mapping['assiette-sup-l'])); 150 | 151 | router.get('/assiette-sup-s', cors(corsOptionsGlobal),categoriesValidators, createGpuProxy(mapping['assiette-sup-s'])); 152 | router.post('/assiette-sup-s', cors(corsOptionsGlobal),categoriesValidators, createGpuProxy(mapping['assiette-sup-s'])); 153 | 154 | /*-------------------------------------------------------------------------------------------- 155 | * Generateur sup 156 | -------------------------------------------------------------------------------------------*/ 157 | 158 | router.get('/generateur-sup-p', cors(corsOptionsGlobal),categoriesValidators,createGpuProxy(mapping['generateur-sup-p'])); 159 | router.post('/generateur-sup-p', cors(corsOptionsGlobal),categoriesValidators,createGpuProxy(mapping['generateur-sup-p'])); 160 | 161 | router.get('/generateur-sup-l', cors(corsOptionsGlobal),categoriesValidators, createGpuProxy(mapping['generateur-sup-l'])); 162 | router.post('/generateur-sup-l', cors(corsOptionsGlobal),categoriesValidators, createGpuProxy(mapping['generateur-sup-l'])); 163 | 164 | router.get('/generateur-sup-s', cors(corsOptionsGlobal),categoriesValidators, createGpuProxy(mapping['generateur-sup-s'])); 165 | router.post('/generateur-sup-s', cors(corsOptionsGlobal),categoriesValidators, createGpuProxy(mapping['generateur-sup-s'])); 166 | 167 | /*-------------------------------------------------------------------------------------------- 168 | * Recherche dans toutes les tables par geom... 169 | -------------------------------------------------------------------------------------------*/ 170 | router.get('/all', cors(corsOptionsGlobal), [ 171 | check('geom').optional().custom(isGeometry), 172 | ], validateParams, gpuWfsClient, function(req,res){ 173 | /** 174 | * Récupération des paramètres 175 | */ 176 | var params = matchedData(req); 177 | // Limite de 500 par type 178 | params._limit = 500; 179 | 180 | /** 181 | * Préparation d'une série de sous-requêtes 182 | */ 183 | var promises = []; 184 | for ( var name in mapping ){ 185 | var typeName = mapping[name]; 186 | 187 | var promise = new Promise(function(resolve, reject) { 188 | req.gpuWfsClient.getFeatures(typeName, params) 189 | .then(function(featureCollection) { 190 | featureCollection.featureType = name; 191 | resolve(featureCollection); 192 | }) 193 | .catch(function(err) { 194 | err.featureType = typeName; 195 | reject(err); 196 | }) 197 | ; 198 | }); 199 | 200 | promises.push(promise); 201 | } 202 | 203 | /** 204 | * Exécution des sous-requêtes et renvoi du résultat 205 | */ 206 | Promise.all(promises).then(function(result){ 207 | res.json(result); 208 | }).catch(function(err){ 209 | res.status(500).json(err); 210 | }); 211 | }); 212 | 213 | 214 | export {router}; 215 | -------------------------------------------------------------------------------- /controllers/health/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import format from 'pg-format'; 3 | import pgClient from '../../middlewares/pgClient.js'; 4 | 5 | var router = new Router(); 6 | 7 | /** 8 | * Contrôle de l'accès à la BDD 9 | */ 10 | router.get('/db', pgClient, function(req, res, next) { 11 | var sql = format('SELECT * FROM pg_stat_activity LIMIT 1'); 12 | req.pgClient.query(sql,function(err,result){ 13 | if(result) return res.status(200).send({status: 'success'}); 14 | if (err) return next(err); 15 | }); 16 | }); 17 | 18 | export {router}; 19 | -------------------------------------------------------------------------------- /controllers/nature/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import gppWfsClient from '../../middlewares/naturegppWfsClient.js'; 7 | import _ from 'lodash'; 8 | 9 | 10 | var router = new Router(); 11 | /** 12 | * Creation d'une chaîne de proxy sur le geoportail 13 | * @param {String} featureTypeName le nom de la couche WFS 14 | */ 15 | function createNaturaProxy(featureTypeName){ 16 | return [ 17 | gppWfsClient, 18 | validateParams, 19 | function(req,res){ 20 | var params = matchedData(req); 21 | params = _.omit(params,'apikey'); 22 | // Tranformation de la géométrie dans le réferentiel 3857 23 | 24 | /* Value default pour _limit an _start */ 25 | if ( typeof params._start == 'undefined' ) { params._start = 0;} 26 | if( typeof params._limit == 'undefined') {params._limit = 1000;} 27 | 28 | /* requête WFS GPP*/ 29 | req.gppWfsClient.getFeatures(featureTypeName, params) 30 | /* uniformisation des attributs en sortie */ 31 | .then(function(featureCollection){ 32 | if(featureCollection.links && featureCollection.links.length) { 33 | for(let i in featureCollection.links) { 34 | if(featureCollection.links[i].href && featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)) { 35 | let num = featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)[0].replace('STARTINDEX=',''); 36 | let href = req.gppWfsClient.headers.Referer.replace(/\/api.*/, '') + req.originalUrl; 37 | if(href.match('_start')) { 38 | href = href.replace(/_start\=[0-9]*/, '_start=' + num); 39 | } else { 40 | href += '&_start=' + num; 41 | } 42 | featureCollection.links[i].href = href; 43 | } 44 | } 45 | } 46 | return featureCollection; 47 | }) 48 | .then(function(featureCollection) { 49 | res.json(featureCollection); 50 | }) 51 | .catch(function(err) { 52 | res.status(500).json(err); 53 | }) 54 | ; 55 | } 56 | ]; 57 | } 58 | 59 | 60 | var corsOptionsGlobal = function(origin,callback) { 61 | var corsOptions; 62 | if (origin) { 63 | corsOptions = { 64 | origin: origin, 65 | optionsSuccessStatus: 200, 66 | methods: 'GET,POST', 67 | credentials: true 68 | }; 69 | } else { 70 | corsOptions = { 71 | origin : '*', 72 | optionsSuccessStatus : 200, 73 | methods: 'GET,POST', 74 | credentials: true 75 | }; 76 | } 77 | callback(null, corsOptions); 78 | }; 79 | 80 | /** 81 | * Permet d'alerter en cas de paramètre ayant changer de nom 82 | * 83 | * TODO Principe à valider (faire un middleware de renommage des paramètres si l'approche est trop violente) 84 | */ 85 | 86 | 87 | var natureValidators = [ 88 | check('geom').optional().custom(isGeometry), 89 | check('_limit').optional().isNumeric(), 90 | check('_start').optional().isNumeric() 91 | ]; 92 | 93 | /** 94 | * Récupération des couches natura 2000 suivant au titre la directive Habitat 95 | * 96 | */ 97 | 98 | var naturaValidators = natureValidators.concat([ 99 | check('sitecode').optional().isString(), 100 | check('sitename').optional().isString() 101 | ]); 102 | 103 | router.get('/natura-habitat', cors(corsOptionsGlobal),naturaValidators, createNaturaProxy('PROTECTEDAREAS.SIC:sic')); 104 | router.post('/natura-habitat',cors(corsOptionsGlobal),naturaValidators, createNaturaProxy('PROTECTEDAREAS.SIC:sic')); 105 | 106 | /** 107 | * Récupération des couches natura 2000 suivant au titre de la directive Oiseaux 108 | * 109 | */ 110 | 111 | router.get('/natura-oiseaux', cors(corsOptionsGlobal),naturaValidators, createNaturaProxy('PROTECTEDAREAS.ZPS:zps')); 112 | router.post('/natura-oiseaux', cors(corsOptionsGlobal),naturaValidators, createNaturaProxy('PROTECTEDAREAS.ZPS:zps')); 113 | 114 | /** 115 | * Récupération des couches sur les réserves naturelle Corse 116 | */ 117 | 118 | var reserveValidators = natureValidators.concat([ 119 | check('id_mnhn').optional().isAlphanumeric(), 120 | check('nom').optional().isString() 121 | ]); 122 | 123 | /** 124 | * Récupération des couches reserves naturelles Corse 125 | * 126 | */ 127 | 128 | router.get('/rnc', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.RNC:rnc')); 129 | router.post('/rnc', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.RNC:rnc')); 130 | 131 | /** 132 | * Récupération des couches reserves naturelles hors Corse 133 | * 134 | */ 135 | 136 | router.get('/rnn', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.RNN:rnn')); 137 | router.post('/rnn', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.RNN:rnn')); 138 | 139 | /** 140 | * Récupération des couches Zones écologiques de nature remarquable 141 | * 142 | */ 143 | router.get('/znieff1',cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.ZNIEFF1:znieff1')); 144 | router.post('/znieff1', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.ZNIEFF1:znieff1')); 145 | 146 | router.get('/znieff2', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.ZNIEFF2:znieff2')); 147 | router.post('/znieff2', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.ZNIEFF2:znieff2')); 148 | 149 | /** 150 | * Récupération des couches Parcs naturels 151 | * 152 | */ 153 | 154 | router.get('/pn', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.PN:pn')); 155 | router.post('/pn', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.PN:pn')); 156 | 157 | /** 158 | * Récupération des couches Parcs naturels régionaux 159 | * 160 | */ 161 | 162 | router.get('/pnr', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.PNR:pnr')); 163 | router.post('/pnr', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.PNR:pnr')); 164 | 165 | /** 166 | * Récupération des couches réserves nationales de chasse et de faune sauvage 167 | * 168 | */ 169 | 170 | router.get('/rncf', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.RNCF:rncfs')); 171 | router.post('/rncf', cors(corsOptionsGlobal),reserveValidators, createNaturaProxy('PROTECTEDAREAS.RNCF:rncfs')); 172 | 173 | export {router}; 174 | -------------------------------------------------------------------------------- /controllers/rpg/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import gppWfsClient from '../../middlewares/gppWfsClient.js'; 7 | import _ from 'lodash'; 8 | import NodeCache from 'node-cache'; 9 | 10 | const myCache = new NodeCache(); 11 | 12 | var router = new Router(); 13 | const lastYearRPG = 2022; 14 | const firstYearRPG = 2010; 15 | 16 | /** 17 | * Creation d'une chaîne de proxy sur le geoportail 18 | * @param {String} valeurSearch du chemin le nom de la couche WFS 19 | */ 20 | function createRpgProxy(valeurSearch) { 21 | return [ 22 | gppWfsClient, 23 | validateParams, 24 | function(req,res){ 25 | var params = matchedData(req); 26 | var featureTypeName= ''; 27 | params = _.omit(params,'apikey'); 28 | /* Modification année dans le flux */ 29 | if (valeurSearch == 'v1') { 30 | if ((params.annee >= firstYearRPG) && (params.annee <= 2014)) { 31 | featureTypeName = 'RPG.' + params.annee + ':rpg_' + params.annee; 32 | } else { 33 | return res.status(400).send({ 34 | code: 400, 35 | message: 'Année Invalide : Valeur uniquement entre ' + firstYearRPG + ' et 2014' 36 | }); 37 | } 38 | } else { 39 | if ((params.annee >= 2015) && (params.annee <= lastYearRPG)) { 40 | featureTypeName = 'RPG.' + params.annee + ':parcelles_graphiques'; 41 | } else { 42 | return res.status(400).send({ 43 | code: 400, 44 | message: 'Année Invalide : Valeur uniquement entre 2015 et ' + lastYearRPG 45 | }); 46 | 47 | } 48 | } 49 | /* Supprimer annee inutile ensuite de params */ 50 | params = _.omit(params,'annee'); 51 | 52 | /* Value default pour _limit an _start */ 53 | if ( typeof params._start == 'undefined' ) {params._start = 0;} 54 | if( typeof params._limit == 'undefined') {params._limit = 1000;} 55 | 56 | //recherche dans le cache 57 | if(myCache.get(featureTypeName)) { 58 | req.gppWfsClient.defaultGeomFieldName = myCache.get(featureTypeName)[0]; 59 | req.gppWfsClient.defaultCRS = myCache.get(featureTypeName)[1]; 60 | 61 | //récupération des features 62 | getFeat(req, res, featureTypeName, params); 63 | } 64 | else { 65 | /* requête WFS GPP*/ 66 | req.gppWfsClient.getDescribeFeatureType(featureTypeName) 67 | //récupération du geomFieldName 68 | .then(function(featureCollection) { 69 | var nom_geom = false; 70 | for(var i in featureCollection.featureTypes[0].properties) { 71 | if(featureCollection.featureTypes[0].properties[i].name == 'geom' 72 | || featureCollection.featureTypes[0].properties[i].name == 'the_geom') 73 | { 74 | nom_geom = featureCollection.featureTypes[0].properties[i].name; 75 | break; 76 | } 77 | } 78 | if(!nom_geom) { 79 | for(var i in featureCollection.featureTypes[0].properties) { 80 | if(featureCollection.featureTypes[0].properties[i].type.match('Point') 81 | || featureCollection.featureTypes[0].properties[i].type.match('Polygon') 82 | || featureCollection.featureTypes[0].properties[i].type.match('LineString')) 83 | { 84 | nom_geom = featureCollection.featureTypes[0].properties[i].name; 85 | } 86 | } 87 | } 88 | 89 | req.gppWfsClient.defaultGeomFieldName = nom_geom; 90 | 91 | //récupération du CRS 92 | req.gppWfsClient.getCapabilities() 93 | .then(function(response){ 94 | var crs = 'urn:ogc:def:crs:EPSG::4326'; 95 | var regexp = new RegExp('' + featureTypeName + '.*?<\/DefaultCRS>'); 96 | if(response.match(regexp)) { 97 | var feat = response.match(regexp)[0]; 98 | if(feat.match(/EPSG::[0-9]{4,5}/)) { 99 | crs = feat.match(/EPSG::[0-9]{4,5}/)[0].replace('::',':'); 100 | } 101 | } 102 | if(crs == 'EPSG:4326') { 103 | crs = 'urn:ogc:def:crs:EPSG::4326'; 104 | } 105 | req.gppWfsClient.defaultCRS = crs; 106 | 107 | //maj du cache 108 | myCache.set(featureTypeName, [nom_geom, crs]); 109 | 110 | //récupération des features 111 | getFeat(req, res, featureTypeName, params); 112 | }) 113 | .catch(function(err) { 114 | res.status(500).json(err); 115 | }) 116 | ; 117 | 118 | 119 | }) 120 | .catch(function(err) { 121 | res.status(500).json(err); 122 | }) 123 | ; 124 | } 125 | } 126 | ]; 127 | } 128 | 129 | var getFeat = function(req, res, featureTypeName, params) { 130 | req.gppWfsClient.getFeatures(featureTypeName, params) 131 | /* uniformisation des attributs en sortie */ 132 | .then(function(featureCollection){ 133 | if(featureCollection.links && featureCollection.links.length) { 134 | for(let i in featureCollection.links) { 135 | if(featureCollection.links[i].href && featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)) { 136 | let num = featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)[0].replace('STARTINDEX=',''); 137 | let href = req.gppWfsClient.headers.Referer.replace(/\/api.*/, '') + req.originalUrl; 138 | if(href.match('_start')) { 139 | href = href.replace(/_start\=[0-9]*/, '_start=' + num); 140 | } else { 141 | href += '&_start=' + num; 142 | } 143 | featureCollection.links[i].href = href; 144 | } 145 | } 146 | } 147 | return featureCollection; 148 | }) 149 | .then(function(featureCollection) { 150 | res.json(featureCollection); 151 | }) 152 | .catch(function(err) { 153 | res.status(500).json(err); 154 | }) 155 | ; 156 | }; 157 | 158 | var corsOptionsGlobal = function(origin,callback) { 159 | var corsOptions; 160 | if (origin) { 161 | corsOptions = { 162 | origin: origin, 163 | optionsSuccessStatus: 200, 164 | methods: 'GET,POST', 165 | credentials: true 166 | }; 167 | } else { 168 | corsOptions = { 169 | origin : '*', 170 | optionsSuccessStatus : 200, 171 | methods: 'GET,POST', 172 | credentials: true 173 | }; 174 | } 175 | callback(null, corsOptions); 176 | }; 177 | 178 | /** 179 | * Permet d'alerter en cas de paramètre ayant changer de nom 180 | * 181 | * TODO Principe à valider (faire un middleware de renommage des paramètres si l'approche est trop violente) 182 | */ 183 | var rpgValidators = [ 184 | check('annee').exists().isNumeric().isLength({min:4,max:4}).withMessage('Année sur 4 chiffres'), 185 | check('code_cultu').optional().isString(), 186 | check('geom').exists().custom(isGeometry).withMessage('La géométrie est invalide.'), 187 | check('_limit').optional().isNumeric(), 188 | check('_start').optional().isNumeric() 189 | ]; 190 | 191 | /** Nous avons 2 requetes identiques mais il y a une difference dans les champs 192 | * Possibilité de traiter différement par la suite. 193 | * /v1 : corresponds aux années avant 2015 194 | * /v2 : corresponds aux années à partir de 2015 195 | */ 196 | router.get('/v1', cors(corsOptionsGlobal),rpgValidators, createRpgProxy('v1')); 197 | router.post('/v1', cors(corsOptionsGlobal),rpgValidators, createRpgProxy('V1')); 198 | 199 | router.get('/v2', cors(corsOptionsGlobal),rpgValidators, createRpgProxy('v2')); 200 | router.post('/v2', cors(corsOptionsGlobal),rpgValidators, createRpgProxy('V2')); 201 | 202 | 203 | 204 | export {router}; 205 | -------------------------------------------------------------------------------- /controllers/wfs-geoportail/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import cors from 'cors'; 3 | import { check, matchedData } from 'express-validator'; 4 | import validateParams from '../../middlewares/validateParams.js'; 5 | import isGeometry from '../../checker/isGeometry.js'; 6 | import gppWfsClient from '../../middlewares/gppWfsClient.js'; 7 | import _ from 'lodash'; 8 | import NodeCache from 'node-cache'; 9 | 10 | const myCache = new NodeCache(); 11 | 12 | var router = new Router(); 13 | 14 | /** 15 | * Creation d'une chaîne de proxy sur le geoportail 16 | * @param {String} valeurSearch du chemin le nom de la couche WFS 17 | */ 18 | function createWfsProxy() { 19 | return [ 20 | gppWfsClient, 21 | validateParams, 22 | function(req,res){ 23 | var params = matchedData(req); 24 | var featureTypeName = params.source; 25 | params = _.omit(params,'source'); 26 | /* Value default pour _limit an _start */ 27 | if ( typeof params._start == 'undefined' ) {params._start = 0;} 28 | if( typeof params._limit == 'undefined') {params._limit = 1000;} 29 | 30 | //recherche dans le cache 31 | if(myCache.get(featureTypeName)) { 32 | req.gppWfsClient.defaultGeomFieldName = myCache.get(featureTypeName)[0]; 33 | req.gppWfsClient.defaultCRS = myCache.get(featureTypeName)[1]; 34 | 35 | //récupération des features 36 | getFeat(req, res, featureTypeName, params); 37 | } 38 | else { 39 | /* requête WFS GPP*/ 40 | req.gppWfsClient.getDescribeFeatureType(featureTypeName) 41 | //récupération du geomFieldName 42 | .then(function(featureCollection) { 43 | var nom_geom = false; 44 | for(var i in featureCollection.featureTypes[0].properties) { 45 | if(featureCollection.featureTypes[0].properties[i].name == 'geom' 46 | || featureCollection.featureTypes[0].properties[i].name == 'the_geom') 47 | { 48 | nom_geom = featureCollection.featureTypes[0].properties[i].name; 49 | break; 50 | } 51 | } 52 | if(!nom_geom) { 53 | for(var i in featureCollection.featureTypes[0].properties) { 54 | if(featureCollection.featureTypes[0].properties[i].type.match('Point') 55 | || featureCollection.featureTypes[0].properties[i].type.match('Polygon') 56 | || featureCollection.featureTypes[0].properties[i].type.match('LineString')) 57 | { 58 | nom_geom = featureCollection.featureTypes[0].properties[i].name; 59 | } 60 | } 61 | } 62 | 63 | req.gppWfsClient.defaultGeomFieldName = nom_geom; 64 | 65 | //récupération du CRS 66 | req.gppWfsClient.getCapabilities() 67 | .then(function(response){ 68 | var crs = 'urn:ogc:def:crs:EPSG::4326'; 69 | var regexp = new RegExp('' + featureTypeName + '.*?<\/DefaultCRS>'); 70 | if(response.match(regexp)) { 71 | var feat = response.match(regexp)[0]; 72 | if(feat.match(/EPSG::[0-9]{4,5}/)) { 73 | crs = feat.match(/EPSG::[0-9]{4,5}/)[0].replace('::',':'); 74 | } 75 | } 76 | if(crs == 'EPSG:4326') { 77 | crs = 'urn:ogc:def:crs:EPSG::4326'; 78 | } 79 | req.gppWfsClient.defaultCRS = crs; 80 | 81 | //maj du cache 82 | myCache.set(featureTypeName, [nom_geom, crs]); 83 | 84 | //récupération des features 85 | getFeat(req, res, featureTypeName, params); 86 | }) 87 | .catch(function(err) { 88 | res.status(500).json(err); 89 | }); 90 | 91 | 92 | }) 93 | .catch(function(err) { 94 | res.status(500).json(err); 95 | }); 96 | } 97 | } 98 | ]; 99 | } 100 | 101 | var getFeat = function(req, res, featureTypeName, params) { 102 | req.gppWfsClient.getFeatures(featureTypeName, params) 103 | /* uniformisation des attributs en sortie */ 104 | .then(function(featureCollection){ 105 | featureCollection.features.forEach(function(feature){ 106 | if ( ! feature.properties.code_insee ){ 107 | feature.properties.code_insee = feature.properties.code_dep+feature.properties.code_com; 108 | } 109 | }); 110 | if(featureCollection.links && featureCollection.links.length) { 111 | for(let i in featureCollection.links) { 112 | if(featureCollection.links[i].href && featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)) { 113 | let num = featureCollection.links[i].href.match(/STARTINDEX\=[0-9]*/)[0].replace('STARTINDEX=',''); 114 | let href = req.gppWfsClient.headers.Referer.replace(/\/api.*/, '') + req.originalUrl; 115 | if(href.match('_start')) { 116 | href = href.replace(/_start\=[0-9]*/, '_start=' + num); 117 | } else { 118 | href += '&_start=' + num; 119 | } 120 | featureCollection.links[i].href = href; 121 | } 122 | } 123 | } 124 | return featureCollection; 125 | }) 126 | .then(function(featureCollection) { 127 | res.json(featureCollection); 128 | }) 129 | .catch(function(err) { 130 | res.status(500).json(err); 131 | }) 132 | ; 133 | }; 134 | 135 | 136 | var corsOptionsGlobal = function(origin,callback) { 137 | var corsOptions; 138 | if (origin) { 139 | corsOptions = { 140 | origin: origin, 141 | optionsSuccessStatus: 200, 142 | methods: 'GET,POST', 143 | credentials: true 144 | }; 145 | } else { 146 | corsOptions = { 147 | origin : '*', 148 | optionsSuccessStatus : 200, 149 | methods: 'GET,POST', 150 | credentials: true 151 | }; 152 | } 153 | callback(null, corsOptions); 154 | }; 155 | 156 | /** 157 | * Permet d'alerter en cas de paramètre ayant changer de nom 158 | * 159 | * TODO Principe à valider (faire un middleware de renommage des paramètres si l'approche est trop violente) 160 | */ 161 | var moduleValidator = [ 162 | check('source').exists().withMessage('Le paramètre source pour le nom de la couche WFS géoportail est obligatoire'), 163 | check('geom').optional().custom(isGeometry), 164 | check('_limit').optional().isNumeric(), 165 | check('_start').optional().isNumeric() 166 | ]; 167 | 168 | 169 | 170 | router.get('/search', cors(corsOptionsGlobal),moduleValidator, createWfsProxy()); 171 | 172 | 173 | 174 | export {router}; 175 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/data/.gitkeep -------------------------------------------------------------------------------- /datasets/appellations-viticoles/config.js: -------------------------------------------------------------------------------- 1 | var aocConfig = { 2 | version: '16 mai 2024', 3 | modules: ["Appellations viticoles"], 4 | nom_url :"FranceAgriMer", 5 | url: 'https://www.franceagrimer.fr/filieres-vin-et-cidre/vin/professionnels/teleprocedures' 6 | }; 7 | 8 | export {aocConfig}; -------------------------------------------------------------------------------- /datasets/base-adresse-nationale/config.js: -------------------------------------------------------------------------------- 1 | var banConfig = { 2 | version: 'v4.1.2', 3 | modules: ["Codes Postaux"], 4 | nom_url :"BAN", 5 | url: 'https://github.com/baseadressenationale/codes-postaux' 6 | }; 7 | 8 | export {banConfig}; -------------------------------------------------------------------------------- /datasets/geoportail/config.js: -------------------------------------------------------------------------------- 1 | var geoportailConfig = { 2 | version: 'Flux WFS', 3 | modules: ["Cadastre", "RPG", "Nature", "WFS-Geoportail"], 4 | nom_url :"Geoservices", 5 | url: 'https://geoservices.ign.fr/services-web-experts' 6 | }; 7 | 8 | export {geoportailConfig}; -------------------------------------------------------------------------------- /datasets/gpu/config.js: -------------------------------------------------------------------------------- 1 | var gpuConfig = { 2 | version: 'Flux WFS', 3 | modules: ["GPU"], 4 | nom_url :"Géoportail de l'urbanisme", 5 | url: 'https://www.geoportail-urbanisme.gouv.fr' 6 | }; 7 | 8 | export {gpuConfig}; -------------------------------------------------------------------------------- /datasets/index.js: -------------------------------------------------------------------------------- 1 | import {geoportailConfig} from './geoportail/config.js'; 2 | import {gpuConfig}from './gpu/config.js'; 3 | import {banConfig} from './base-adresse-nationale/config.js'; 4 | //import {aocConfig} from './appellations-viticoles/config.js'; 5 | 6 | 7 | var datasets = { 8 | 'Géoplateforme': geoportailConfig, 9 | 'GPU': gpuConfig, 10 | 'Base adresse nationale': banConfig 11 | //'Base des appellations viticoles' : aocConfig 12 | }; 13 | 14 | export {datasets}; -------------------------------------------------------------------------------- /dev/docker.md: -------------------------------------------------------------------------------- 1 | # APICARTO - déploiement avec docker 2 | 3 | ## Points clés 4 | 5 | * L'image apicarto est définie par le fichier [Dockerfile](../Dockerfile) 6 | * Un exemple de fichier [docker-compose.yml](../docker-compose.yml) est disponible 7 | * Le nom du conteneur PostGIS y est fixé à apicarto-db (simplifie les commandes ci-dessous et ne gêne pas car non scalable) 8 | * Ce conteneur PostGIS est utile uniquement pour le module AOC 9 | 10 | ## Construction et installation 11 | 12 | * Construire l'image apicarto : `docker-compose build` 13 | * Démarrer la stack : 14 | * `docker-compose up -d` => http://devbox.ign.fr:8091/api/doc/ 15 | * `HOST_HOSTNAME=devbox.ign.fr docker-compose up -d` => https://apicarto.devbox.ign.fr/api/doc/ 16 | 17 | ## Gestion de la base de données 18 | 19 | Pour le module AOC : 20 | 21 | * Création de la base de données : `docker exec -i apicarto-db createdb -U postgres apicarto` 22 | * Activation de l'extension PostGIS : `docker exec -i apicarto-db psql -U postgres -d apicarto -c "CREATE EXTENSION postgis"` 23 | * Restaurer une sauvegarde : 24 | 25 | ```bash 26 | cat $APICARTO_FTP_DIR/backup/apicarto_20220829.sql | docker exec -i apicarto-db psql -U postgres -d apicarto 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /doc/aoc.yml: -------------------------------------------------------------------------------- 1 | openapi: '3.1.0' 2 | 3 | info: 4 | version: '2.8.7' 5 | title: API Carto Appellations viticoles 6 | description: > 7 | Le module appellations viticoles permet de récupérer les zones intersectant une géométrie GeoJSON. 8 | 9 | Le module utilise une base de données entretenue par FranceAgriMer. Cette base de données regroupe : 10 | 11 | * Les zones d’appellation d’origine contrôlées (AOC) 12 | 13 | * Les zones d’indications géographiques protégées (IGP) 14 | 15 | * Les zones viticoles sans indication géographique (VSIG) 16 | 17 | 18 | Consultez la [documentation utilisateur](https://apicarto.ign.fr/api/doc/pdf/docUser_moduleAoc.pdf) pour plus d’informations sur les paramètres d’appel disponibles et le format des résultats. 19 | 20 | Dernière mise à jour des données : 10 Mars 2025 21 | 22 | ## Géométrie 23 | 24 | La géométrie doit être exprimée en valeur décimale dans le référentiel WGS84. 25 | 26 | 27 | **Exemples de géométrie** : (référentiel EPSG:4326) 28 | 29 | * Point : 30 | 31 | `{"type": "Point","coordinates":[-1.691634,48.104237]}` 32 | 33 | * MultiPolygon : 34 | 35 | `{"type":"MultiPolygon","coordinates":[[[[-0.288863182067871,48.963666607295977],[-0.299592018127441,48.959299208576141],[-0.296330451965332,48.955325952385039],[-0.282125473022461,48.950675995388366],[-0.279722213745117,48.967019382922331],[-0.288863182067871,48.963666607295977]]]]}` 36 | 37 | * Surface trouée : 38 | 39 | `{"type":"Polygon","coordinates":[[[1.2,48.85],[1.3,48.85],[1.3,48.9],[1.2,48.9],[1.2,48.85]],[[1.23,48.86],[1.23,48.88],[1.26,48.88],[1.26,48.86],[1.23,48.86]]]}` 40 | 41 | * Linéaire : 42 | 43 | `{"type":"LineString","coordinates":[[4.681549,47.793784],[4.741974,47.788248]]}` 44 | 45 | ## Historique des changements 46 | * utilisation du flux WFS privé de FranceAgriMer 47 | * ajout du paramètre *apikey* 48 | 49 | servers: 50 | - url: '/' 51 | paths: 52 | /api/aoc/appellation-viticole: 53 | post: 54 | operationId: postAppellationViticole 55 | description: | 56 | Prend une geometrie de type GeoJSON en paramètre d'entrée et renvoie les appellations viticoles intersectantes 57 | parameters: 58 | - name: geom 59 | in: query 60 | required: true 61 | schema: 62 | $ref: '#/components/schemas/Geometry' 63 | - name: apikey 64 | in: query 65 | required: true 66 | schema: 67 | type: string 68 | 69 | 70 | tags: 71 | - Appellations viticoles 72 | responses: 73 | '200': 74 | description: 'Succès' 75 | content: 76 | application/json: 77 | schema: 78 | type: array 79 | items: 80 | $ref: '#/components/schemas/FeatureCollectionAppellationViticole' 81 | '400': 82 | description: "Paramètres invalide" 83 | content: 84 | schema: 85 | type: array 86 | items: 87 | $ref: "#/definitions/Error" 88 | '500': 89 | description: "Erreur dans le traitement de la requête" 90 | schema: 91 | $ref: '#/components/schemas/Error' 92 | 93 | components: 94 | schemas: 95 | Geometry: 96 | type: string 97 | description: Une `Geometry` au sens GeoJSON 98 | properties: 99 | type: 100 | type: string 101 | description: Le type géométrique 102 | enum: 103 | - Point 104 | - LineString 105 | - Polygon 106 | - MultiPoint 107 | - MultiLineString 108 | - MultiPolygon 109 | coordinates: 110 | type: array 111 | items: {} 112 | Feature: 113 | type: object 114 | description: Une `Feature` au sens GeoJSON 115 | properties: 116 | type: 117 | type: string 118 | description: Le type d'objet GeoJSON (`Feature`) 119 | id: 120 | type: string 121 | geometry: 122 | $ref: '#/components/schemas/Geometry' 123 | MultiPolygon: 124 | type: object 125 | description: Un `MultiPolygon` au sens GeoJSON 126 | properties: 127 | type: 128 | type: string 129 | description: Le type géométrique (`MultiPolygon`) 130 | coordinates: 131 | type: array 132 | items: {} 133 | 134 | FeatureAppellationViticole: 135 | description: Objet géographique appellation viticole 136 | type: object 137 | allOf: 138 | - $ref: "#/components/schemas/Feature" 139 | properties: 140 | properties: 141 | type: object 142 | properties: 143 | appelation: 144 | type: string 145 | idapp: 146 | type: string 147 | id_uni: 148 | type: string 149 | description: corresponds à "segment-idapp-insee" 150 | insee: 151 | type: string 152 | segment: 153 | type: string 154 | instruction_obligatoire: 155 | type: boolean 156 | granularite: 157 | type: string 158 | enum: 159 | - commune 160 | - exacte 161 | appellation: 162 | type: string 163 | contains: 164 | type: boolean 165 | geometry: 166 | $ref: '#/components/schemas/MultiPolygon' 167 | 168 | FeatureCollectionAppellationViticole: 169 | description: "Liste d'objet géographique appellation viticole" 170 | type: object 171 | properties: 172 | type: 173 | type: string 174 | enum: 175 | - FeatureCollection 176 | features: 177 | type: array 178 | items: 179 | $ref: '#/components/schemas/FeatureAppellationViticole' 180 | 181 | Error: 182 | required: 183 | - code 184 | - message 185 | properties: 186 | code: 187 | type: string 188 | message: 189 | type: string -------------------------------------------------------------------------------- /doc/bduni.yml: -------------------------------------------------------------------------------- 1 | openapi: '3.1.0' 2 | 3 | info: 4 | version: '2.8.7' 5 | title: Module BDUni 6 | description: > 7 | 8 | Le service d'interrogation BDUni permet d'obtenir des informations sur les tronçons de route les plus proches d'un point. 9 | 10 | Toutes les réponses sont au format GeoJSON et de type FeatureCollection. 11 | 12 | Toutes les requêtes du module BDUni doivent se faire en GET. 13 | 14 | 15 | **Exemple de géométrie : (référentiel EPSG:4326)** 16 | 17 | * Point: 18 | 19 | `{"type": "Point","coordinates":[-1.691634,48.104237]}` 20 | 21 | contact: 22 | name: API Carto BDUni 23 | 24 | servers: 25 | - url: / 26 | 27 | paths: 28 | /api/bduni/troncon: 29 | get: 30 | operationId: getTroncon 31 | summary: Récupération des tronçons de route les plus proches 32 | description: | 33 | Renvoie une liste de résultats contenant les données d'un troncon de route bduni à partir d'une coordonnée en longitude/latitude. 34 | Il faut obligatoirement renseiger les champs "lon" et "lat" ou le champ "geom" (mais pas les deux en même temps). 35 | parameters: 36 | 37 | - name: lon 38 | in: query 39 | description: 'Longitude (ex : 4.829214)' 40 | required: false 41 | schema: 42 | type: number 43 | 44 | 45 | - name: lat 46 | in: query 47 | description: 'Latitude (ex : 45.996981)' 48 | required: false 49 | schema: 50 | type: number 51 | 52 | 53 | - name: distance 54 | in: query 55 | description: 'Distance de recherche en mètres' 56 | required: false 57 | default : 100 58 | placeholder: 100 59 | schema: 60 | type: number 61 | 62 | 63 | - name: geom 64 | in: query 65 | description: Géométrie au format GeoJson 66 | schema: 67 | $ref: '#/components/schemas/Geometry' 68 | 69 | - name: _limit 70 | in: query 71 | description: Limite de résultats à afficher(chiffre entre 1 et 1000) 72 | required: false 73 | schema: 74 | type: integer 75 | 76 | 77 | - name: _start 78 | in: query 79 | description: Position pour le début de la recherche 80 | required: false 81 | schema: 82 | type: integer 83 | 84 | 85 | responses: 86 | '200': 87 | description: Success 88 | content: 89 | application/json: 90 | schema: 91 | $ref: '#/components/schemas/FeatureCollectionTroncon' 92 | 93 | 94 | components: 95 | schemas: 96 | Coordinate: 97 | type: array 98 | items: 99 | type: number 100 | minItems: 2 101 | maxItems: 4 102 | Geometry: 103 | type: string 104 | description: Une `Geometry` au sens GeoJSON 105 | properties: 106 | type: 107 | type: string 108 | description: Le type géométrique 109 | enum: 110 | - Point 111 | coordinates: 112 | type: array 113 | items: {} 114 | Point: 115 | type: object 116 | description: Un `Point` au sens GeoJSON 117 | properties: 118 | type: 119 | type: string 120 | description: Le type géométrique (`Point`) 121 | enum: 122 | - Point 123 | coordinates: 124 | $ref: '#/components/schemas/Coordinate' 125 | 126 | Feature: 127 | type: object 128 | description: Une `Feature` au sens GeoJSON 129 | properties: 130 | type: 131 | type: string 132 | description: Le type d'objet GeoJSON (`Feature`) 133 | id: 134 | type: string 135 | geometry: 136 | $ref: '#/components/schemas/Geometry' 137 | 138 | FeatureCollection: 139 | type: object 140 | description: Une `FeatureCollection` au sens GeoJSON 141 | properties: 142 | type: 143 | type: string 144 | description: Le type d'objet GeoJSON (`FeatureCollection`) 145 | features: 146 | type: array 147 | items: {} 148 | 149 | FeatureTroncon: 150 | description: 'Un tronçon de route' 151 | type: object 152 | allOf: 153 | - $ref: '#/components/schemas/Feature' 154 | properties: 155 | properties: 156 | type: object 157 | properties: 158 | cleabs: 159 | type: string 160 | cl_admin: 161 | type: string 162 | nature: 163 | type: string 164 | pos_sol: 165 | type: string 166 | importance: 167 | type: string 168 | nb_voies: 169 | type: string 170 | sens: 171 | type: string 172 | largeur: 173 | type: number 174 | gestion: 175 | type: string 176 | numero: 177 | type: string 178 | distance: 179 | type: number 180 | geometry: 181 | $ref: '#/components/schemas/Point' 182 | 183 | FeatureCollectionTroncon: 184 | description: Une `FeatureCollection` contenant uniquement des features de type `FeatureCollectionTroncon` 185 | type: object 186 | properties: 187 | type: 188 | type: string 189 | enum: 190 | - FeatureCollection 191 | features: 192 | type: array 193 | items: 194 | $ref: '#/components/schemas/FeatureTroncon' 195 | -------------------------------------------------------------------------------- /doc/codes-postaux.yml: -------------------------------------------------------------------------------- 1 | openapi: '3.1.0' 2 | info: 3 | version: '2.8.7' 4 | title: API Carto - codes-postaux 5 | description: > 6 | API de récupération des communes associées à un code postal donné. Voir https://github.com/BaseAdresseNationale/codes-postaux pour plus d'information sur les sources de données. 7 | 8 | 9 | Consultez la [documentation utilisateur](https://apicarto.ign.fr/api/doc/pdf/docUser_moduleCodesPostaux.pdf) pour plus d’informations sur les paramètres d’appel disponibles et le format des résultats. 10 | 11 | 12 | Dernière mise à jour des données : 13 Octobre 2024 13 | 14 | 15 | servers: 16 | - url: / 17 | paths: 18 | /api/codes-postaux/communes/{codePostal}: 19 | get: 20 | operationId: getCommunesByCodePostal 21 | description: Renvoie les communes correspondant à un code postal 22 | parameters: 23 | - in: path 24 | name: codePostal 25 | required: true 26 | description: Code postal de la commune. 27 | schema: 28 | type: string 29 | tags: 30 | - codes-postaux 31 | responses: 32 | '200': 33 | description: Success 34 | content: 35 | application/json: 36 | schema: 37 | type: array 38 | items: 39 | $ref: '#/components/schemas/Commune' 40 | 41 | components: 42 | schemas: 43 | Commune: 44 | type: object 45 | description: Commune correspondant au code postal 46 | properties: 47 | codePostal: 48 | type: string 49 | description: Le code postal 50 | codeCommune: 51 | type: string 52 | description: Le code INSEE de la commune 53 | nomCommune: 54 | type: string 55 | description: Le nom de la commune 56 | libelleAcheminement: 57 | type: string 58 | description: Le libellé d'acheminement -------------------------------------------------------------------------------- /doc/corse.yml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Module Ressources Corse 4 | description: > 5 | 6 | Le service d'interrogation du module "corse" permet d'obtenir les informations sur différentes couches fournis par la Dreal Corse 7 | 8 | Toutes les réponses sont au format GeoJSON et de type FeatureCollection. 9 | 10 | Toutes les requêtes du module "corse" peuvent se faire en POST ou en GET. 11 | 12 | Sur cette page, vous pouvez uniquement tester les modules avec des requêtes en GET. 13 | 14 | 15 | contact: 16 | name: Api Carto Dreal Corse 17 | 18 | produces: 19 | - application/json 20 | 21 | basePath: /api 22 | 23 | paths: 24 | /corse/foretcorse: 25 | get: 26 | summary: Recherche des zones en Corse sur les Forêts bénéficiant du régime forestier 27 | description: Retourne un résultat de Type "FeatureCollection" 28 | tags: 29 | - Foret Corse 30 | parameters: 31 | 32 | - name: ccod_frt 33 | in: query 34 | type: string 35 | required: false 36 | 37 | - name: llib_frt 38 | in: query 39 | type: string 40 | required: false 41 | 42 | - name: propriete 43 | in: query 44 | type: string 45 | required: false 46 | 47 | - name: s_sig_ha 48 | in: query 49 | type: string 50 | required: false 51 | 52 | - name: nom_fore 53 | in: query 54 | type: string 55 | required: false 56 | 57 | - name: dpt 58 | in: query 59 | type: string 60 | required: false 61 | 62 | - name: geom 63 | in: query 64 | description: Géométrie au format GeoJson 65 | schema: 66 | $ref: '#/definitions/Geometry' 67 | 68 | responses: 69 | '200': 70 | description: Success 71 | schema: 72 | $ref: '#/definitions/FeatureCollectionCorseForet' 73 | 74 | /corse/pechecorse: 75 | get: 76 | summary: Recherche des zones de pêche en Corse 77 | description: Retourne un résultat de Type "FeatureCollection" 78 | tags: 79 | - Peche Corse 80 | parameters: 81 | - name: dpt 82 | in: query 83 | type: string 84 | required: false 85 | 86 | - name: geom 87 | in: query 88 | description: Géométrie au format GeoJson 89 | schema: 90 | $ref: '#/definitions/Geometry' 91 | 92 | responses: 93 | '200': 94 | description: Success 95 | schema: 96 | $ref: '#/definitions/FeatureCollectionCorsePeche' 97 | 98 | /corse/search: 99 | get: 100 | description: | 101 | Prend une geometrie de type GeoJSON en paramètre d'entrée et renvoie les informations intersectant cette géométrie 102 | Prend la source de donnée issue de https://georchestra.ac-corse.fr/geoserver/ à interroger en paramètre d'entrée 103 | Paramètres de sorties : Retourne un résultat de Type "FeatureCollection" 104 | tags: 105 | - Touslesflux 106 | parameters: 107 | - name: source 108 | in: query 109 | description: Source des données geochestra.ac-corse 110 | type: string 111 | required: true 112 | 113 | - name: geom 114 | in: query 115 | required: true 116 | schema: 117 | $ref: "#/definitions/Geometry" 118 | 119 | responses: 120 | '200': 121 | description: Success 122 | 123 | 124 | definitions: 125 | 126 | #--------------------------------------------------- 127 | # /Corse Forêt/ 128 | #--------------------------------------------------- 129 | 130 | FeatureCorseForet: 131 | description: 'Corse Forêt' 132 | allOf: 133 | - $ref: '#/definitions/Feature' 134 | properties: 135 | properties: 136 | type: object 137 | properties: 138 | ccod_frt: 139 | type: string 140 | llib_frt: 141 | type: string 142 | propriete: 143 | type: string 144 | s_sig_ha: 145 | type: number 146 | nom_fore: 147 | type: string 148 | dept: 149 | type: string 150 | geometry: 151 | $ref: '#/definitions/MultiPolygon' 152 | 153 | 154 | #--------------------------------------------------- 155 | # /corse Pêche 156 | #--------------------------------------------------- 157 | 158 | FeatureCorsePeche: 159 | description: 'Dreal Corse Pêche' 160 | allOf: 161 | - $ref: '#/definitions/Feature' 162 | properties: 163 | properties: 164 | type: object 165 | properties: 166 | designatio: 167 | type: string 168 | dpt: 169 | type: string 170 | date_arr: 171 | type: string 172 | date_fin: 173 | type: string 174 | id_repe: 175 | type: integer 176 | st_length: 177 | type: number 178 | geometry: 179 | $ref: '#/definitions/MultiPolygon' 180 | 181 | FeatureCollectionCorsePeche: 182 | description: Une `FeatureCollection` 183 | allOf: 184 | - $ref: '#/definitions/FeatureCollection' 185 | properties: 186 | features: 187 | type: array 188 | items: 189 | $ref: '#/definitions/FeatureCorsePeche' 190 | 191 | FeatureCollectionCorseForet: 192 | description: Une `FeatureCollection` 193 | allOf: 194 | - $ref: '#/definitions/FeatureCollection' 195 | properties: 196 | features: 197 | type: array 198 | items: 199 | $ref: '#/definitions/FeatureCorseForet' 200 | 201 | # import definitions 202 | FeatureCollection: 203 | $ref: './schema/geojson.yml#definitions/FeatureCollection' 204 | Feature: 205 | $ref: './schema/geojson.yml#definitions/Feature' 206 | Geometry: 207 | $ref: './schema/geojson.yml#definitions/Geometry' 208 | MultiPolygon: 209 | $ref: './schema/geojson.yml#definitions/MultiPolygon' 210 | Point: 211 | $ref: './schema/geojson.yml#definitions/Point' 212 | -------------------------------------------------------------------------------- /doc/faq/calcul-surface.md: -------------------------------------------------------------------------------- 1 | # Comment calculer la surface des parcelles et la surface d'intersection avec la géométrie en entrée? 2 | 3 | Avec `geom` la géométrie de recherche des parcelles et `featureCollection` le résultat de l'appel à l'API `/api/cadastre/parcelle`, il suffit de procéder comme suit à l'aide de turfjs : 4 | 5 | ```javascript 6 | /* pour chaque feature... */ 7 | featureCollection.features.forEach(function(feature){ 8 | // on calcule la surface de la parcelle... 9 | feature.properties.surface = turf.area(feature); 10 | 11 | // on calcule l'intersection géométrique... 12 | var intersection = turf.intersect(feature, geom); 13 | // puis la surface de l'intersection 14 | if ( typeof intersection === 'undefined' ){ 15 | feature.properties.surface_intersection = 0.0; 16 | }else{ 17 | feature.properties.surface_intersection = (turf.area(intersection)).toFixed(2); 18 | } 19 | }); 20 | ``` 21 | 22 | Voir : 23 | 24 | * http://turfjs.org/docs#area 25 | * http://turfjs.org/docs#intersect 26 | -------------------------------------------------------------------------------- /doc/pdf/docUser_moduleAoc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/doc/pdf/docUser_moduleAoc.pdf -------------------------------------------------------------------------------- /doc/pdf/docUser_moduleCadastre.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/doc/pdf/docUser_moduleCadastre.pdf -------------------------------------------------------------------------------- /doc/pdf/docUser_moduleCodesPostaux.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/doc/pdf/docUser_moduleCodesPostaux.pdf -------------------------------------------------------------------------------- /doc/pdf/docUser_moduleNature.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/doc/pdf/docUser_moduleNature.pdf -------------------------------------------------------------------------------- /doc/pdf/docUser_moduleRPG.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/doc/pdf/docUser_moduleRPG.pdf -------------------------------------------------------------------------------- /doc/pdf/docUser_moduleUrbanisme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/doc/pdf/docUser_moduleUrbanisme.pdf -------------------------------------------------------------------------------- /doc/pdf/docUser_moduleWfsGeoportail.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/doc/pdf/docUser_moduleWfsGeoportail.pdf -------------------------------------------------------------------------------- /doc/rpg.yml: -------------------------------------------------------------------------------- 1 | openapi: '3.1.0' 2 | 3 | info: 4 | version: '2.8.7' 5 | title: Module RPG 6 | description: > 7 | 8 | Le service d’interrogation du RPG permet d’obtenir des informations du registre parcellaire graphique intersectant une géométrie (ponctuelle ou surfacique). 9 | 10 | Toutes les réponses sont au format GeoJSON et de type FeatureCollection. 11 | 12 | 13 | Toutes les requêtes du module RPG peuvent se faire en POST ou en GET. 14 | 15 | 16 | Sur cette page, vous pouvez uniquement tester les modules avec des requêtes en GET. 17 | 18 | 19 | Consultez la [documentation utilisateur](https://apicarto.ign.fr/api/doc/pdf/docUser_moduleRPG.pdf) pour plus d’informations sur les paramètres d’appel disponibles et le format des résultats. 20 | 21 | 22 | Les millésimes disponibles sont : 23 | 24 | 25 | **RPG V1** 26 | 27 | RPG.2010:rpg_2010 28 | 29 | RPG.2011:rpg_2011 30 | 31 | RPG.2012:rpg_2012 32 | 33 | RPG.2013:rpg_2013 34 | 35 | RPG.2014:rpg_2014 36 | 37 | 38 | **RPG V2** 39 | 40 | RPG.2015:parcelles_graphiques 41 | 42 | RPG.2016:parcelles_graphiques 43 | 44 | RPG.2017:parcelles_graphiques 45 | 46 | RPG.2018:parcelles_graphiques 47 | 48 | RPG.2019:parcelles_graphiques 49 | 50 | RPG.2020:parcelles_graphiques 51 | 52 | RPG.2021:parcelles_graphiques 53 | 54 | RPG.2022:parcelles_graphiques 55 | 56 | 57 | **Exemple de géométrie** : (référentiel EPSG:4326) 58 | 59 | * Point : 60 | 61 | `{"type": "Point","coordinates":[-1.691634,48.104237]}` 62 | 63 | * MultiPolygon : 64 | 65 | `{"type":"MultiPolygon","coordinates":[[[[-0.288863182067871,48.963666607295977],[-0.299592018127441,48.959299208576141],[-0.296330451965332,48.955325952385039],[-0.282125473022461,48.950675995388366],[-0.279722213745117,48.967019382922331],[-0.288863182067871,48.963666607295977]]]]}` 66 | 67 | * Polygone troué : 68 | 69 | `{"type":"Polygon","coordinates":[[[1.2,48.85],[1.3,48.85],[1.3,48.9],[1.2,48.9],[1.2,48.85]],[[1.23,48.86],[1.23,48.88],[1.26,48.88],[1.26,48.86],[1.23,48.86]]]}` 70 | 71 | * Linéaire : 72 | 73 | `{"type":"LineString","coordinates":[[4.681549,47.793784],[4.741974,47.788248]]}` 74 | 75 | ## Historique des changements: 76 | 77 | * Suppression du paramètre *apikey* 78 | * La géométrie(champ geom) est obligatoire pour les recherches 79 | 80 | 81 | servers: 82 | - url: / 83 | paths: 84 | /api/rpg/v1: 85 | get: 86 | operationId: getV1 87 | description: | 88 | Prend une geometrie de type GeoJSON en paramètre d'entrée et renvoie les informations intersectant cette géométrie 89 | Prend une date qui sera une valeur comprise entre 2010 et 2014 inclus. 90 | Les champs année et geom sont obligatoires. 91 | parameters: 92 | 93 | - name: annee 94 | in: query 95 | required: true 96 | schema: 97 | type: integer 98 | pattern: '\d{4}' 99 | 100 | - name: code_cultu 101 | in: query 102 | required: false 103 | schema: 104 | type: string 105 | 106 | - name: geom 107 | in: query 108 | description: Le champ geom est obligatoire pour la recherche 109 | required: true 110 | schema: 111 | $ref: "#/components/schemas/Geometry" 112 | 113 | - name: _limit 114 | in: query 115 | description: Limite de résultats à afficher (chiffre entre 1 et 1000) 116 | required: false 117 | schema: 118 | type: integer 119 | 120 | - name: _start 121 | in: query 122 | description: Position pour le début de la recherche 123 | required: false 124 | schema: 125 | type: integer 126 | 127 | tags: 128 | - RPG avant 2015 129 | responses: 130 | '200': 131 | description: "Succès" 132 | content: 133 | application/json: 134 | schema: 135 | $ref: "#/components/schemas/FeatureCollectionRPGAvant2015" 136 | 137 | /api/rpg/v2: 138 | get: 139 | operationId: getV2 140 | description: | 141 | Prend une geometrie de type GeoJSON en paramètre d'entrée et renvoie les informations intersectant cette géométrie 142 | Prend une date qui sera une valeur comprise entre 2015 et 2022 inclus. 143 | Les champs année et geom sont obligatoires. 144 | 145 | parameters: 146 | - name: annee 147 | in: query 148 | required: true 149 | schema: 150 | type: integer 151 | pattern: '\d{4}' 152 | 153 | - name: code_cultu 154 | in: query 155 | required: false 156 | schema: 157 | type: string 158 | 159 | - name: geom 160 | in: query 161 | description: Le champ geom est obligatoire pour la recherche 162 | required: true 163 | schema: 164 | $ref: "#/components/schemas/Geometry" 165 | 166 | - name: _limit 167 | in: query 168 | description: Limite de résultats à afficher (chiffre entre 1 et 1000) 169 | required: false 170 | schema: 171 | type: integer 172 | 173 | - name: _start 174 | in: query 175 | description: Position pour le début de la recherche 176 | required: false 177 | schema: 178 | type: integer 179 | 180 | tags: 181 | - RPG à partir de 2015 182 | responses: 183 | '200': 184 | description: "Succès" 185 | content: 186 | application/json: 187 | schema: 188 | $ref: "#/components/schemas/FeatureCollectionRPGApartirde2015" 189 | 190 | components: 191 | schemas: 192 | Coordinate: 193 | type: array 194 | items: 195 | type: number 196 | minItems: 2 197 | maxItems: 4 198 | Geometry: 199 | type: string 200 | description: Une `Geometry` au sens GeoJSON 201 | properties: 202 | type: 203 | type: string 204 | description: Le type géométrique 205 | enum: 206 | - Point 207 | - LineString 208 | - Polygon 209 | - MultiPoint 210 | - MultiLineString 211 | - MultiPolygon 212 | coordinates: 213 | type: array 214 | items: {} 215 | Feature: 216 | type: object 217 | description: Une `Feature` au sens GeoJSON 218 | properties: 219 | type: 220 | type: string 221 | description: Le type d'objet GeoJSON (`Feature`) 222 | id: 223 | type: string 224 | geometry: 225 | $ref: '#/components/schemas/Geometry' 226 | FeatureCollection: 227 | type: object 228 | description: Une `FeatureCollection` au sens GeoJSON 229 | properties: 230 | type: 231 | type: string 232 | description: Le type d'objet GeoJSON (`FeatureCollection`) 233 | features: 234 | type: array 235 | items: {} 236 | MultiPolygon: 237 | type: object 238 | description: Un `MultiPolygon` au sens GeoJSON 239 | properties: 240 | type: 241 | type: string 242 | description: Le type géométrique (`MultiPolygon`) 243 | coordinates: 244 | type: array 245 | items: {} 246 | 247 | FeatureRPGApartirde2015: 248 | description: "Objet géographique RPG à partir de 2015" 249 | type: object 250 | allOf: 251 | - $ref: "#/components/schemas/Feature" 252 | properties: 253 | properties: 254 | type: object 255 | properties: 256 | id_parcel: 257 | type: string 258 | surf_parc: 259 | type: string 260 | code_cultu: 261 | type: string 262 | code_group: 263 | type: string 264 | culture_d1: 265 | type: string 266 | culture_d2: 267 | type: string 268 | 269 | geometry: 270 | $ref: '#/components/schemas/MultiPolygon' 271 | 272 | FeatureCollectionRPGApartirde2015: 273 | description: "Liste d'objet géographique RPG" 274 | type: object 275 | properties: 276 | type: 277 | type: string 278 | enum: 279 | - FeatureCollection 280 | features: 281 | type: array 282 | items: 283 | $ref: '#/components/schemas/FeatureRPGApartirde2015' 284 | 285 | FeatureRPGAvant2015: 286 | description: "Objet géographique RPG avant 2015" 287 | type: object 288 | allOf: 289 | - $ref: "#/components/schemas/Feature" 290 | properties: 291 | properties: 292 | type: object 293 | properties: 294 | num_ilot: 295 | type: string 296 | commune: 297 | type: string 298 | forme_juri: 299 | type: string 300 | surf_decla: 301 | type: string 302 | dep_rattach: 303 | type: string 304 | surf_cultu: 305 | type: string 306 | code_cultu: 307 | type: string 308 | nom_cultu: 309 | type: string 310 | 311 | geometry: 312 | $ref: '#/components/schemas/MultiPolygon' 313 | 314 | FeatureCollectionRPGAvant2015: 315 | description: "Liste d'objet géographique RPG" 316 | type: object 317 | properties: 318 | type: 319 | type: string 320 | enum: 321 | - FeatureCollection 322 | features: 323 | type: array 324 | items: 325 | $ref: '#/components/schemas/FeatureRPGAvant2015' 326 | -------------------------------------------------------------------------------- /doc/views/mentions.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include('partial/head.ejs') %> 6 | 7 | 8 | 9 | 10 | <%- include('partial/menu.ejs') %> 11 | 12 |
13 | 45 |
46 | <%- include('partial/scripts.ejs') %> 47 | 48 | 49 | -------------------------------------------------------------------------------- /doc/views/module.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include('partial/head.ejs') %> 6 | 7 | 8 | 9 | 10 | <%- include('partial/menu.ejs') %> 11 | 12 |
13 |
14 |
15 | 16 | 17 | <%- include('partial/scripts.ejs') %> 18 | 19 | 20 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/views/partial/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | API Carto - Documentation Technique 4 | 5 | 6 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /doc/views/partial/menu.ejs: -------------------------------------------------------------------------------- 1 | 2 | 60 | 61 | -------------------------------------------------------------------------------- /doc/views/partial/scripts.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc/wfs-geoportail.yml: -------------------------------------------------------------------------------- 1 | openapi: '3.1.0' 2 | 3 | info: 4 | version: '2.8.7' 5 | title: Module pour rechercher dans tous les flux WFS Géoportail (Version Bêta) 6 | description: > 7 | 8 | Ce module permet d’intersecter les couche WFS du géoportail. 9 | 10 | Un tri sémantique sur un attribut devra être fait en aval par l'application cliente. 11 | 12 | Toutes les requêtes du module peuvent se faire en POST ou en GET. 13 | 14 | Sur cette page, vous pouvez uniquement tester les modules avec des requêtes en GET. 15 | 16 | 17 | Consultez la [documentation utilisateur](https://apicarto.ign.fr/api/doc/pdf/docUser_moduleWfsGeoportail.pdf) pour plus d’informations sur les paramètres d’appel disponibles et le format des résultats. 18 | 19 | ## Couche source 20 | 21 | Les couches WFS disponibles sur le géoportail peuvent être listée via l’URL [https://data.geopf.fr/wfs/ows?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetCapabilities](https://data.geopf.fr/wfs/ows?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetCapabilities) 22 | 23 | Le nom technique de la couche source doit être utilisé. Celui-ci est indiqué par la balise ``. 24 | 25 | **Exemple pour le champ *source*** : CADASTRALPARCELS.PARCELLAIRE_EXPRESS:parcelle 26 | 27 | ## Géométrie 28 | 29 | La géometrie doit être exprimée en valeur décimale dans le référentiel WGS84 30 | 31 | **Exemples de géométrie** : (référentiel EPSG:4326) 32 | 33 | * Point : 34 | 35 | `{"type": "Point","coordinates":[-1.691634,48.104237]}` 36 | 37 | * MultiPolygon : 38 | 39 | `{"type":"MultiPolygon","coordinates":[[[[-0.288863182067871,48.963666607295977],[-0.299592018127441,48.959299208576141],[-0.296330451965332,48.955325952385039],[-0.282125473022461,48.950675995388366],[-0.279722213745117,48.967019382922331],[-0.288863182067871,48.963666607295977]]]]}` 40 | 41 | * Polygone troué : 42 | 43 | `{"type":"Polygon","coordinates":[[[1.2,48.85],[1.3,48.85],[1.3,48.9],[1.2,48.9],[1.2,48.85]],[[1.23,48.86],[1.23,48.88],[1.26,48.88],[1.26,48.86],[1.23,48.86]]]}` 44 | 45 | * Linéaire : 46 | 47 | `{"type":"LineString","coordinates":[[4.681549,47.793784],[4.741974,47.788248]]}` 48 | 49 | 50 | ## Historique des changements 51 | 52 | * Utilisation des flux WFS de la geoplateforme 53 | * Suppression du paramètre *apikey* 54 | * Suppression des contraintes sur le nom de la géométrie et le CRS 55 | 56 | servers: 57 | - url: / 58 | paths: 59 | /api/wfs-geoportail/search: 60 | get: 61 | operationId: getWfsSearch 62 | description: | 63 | Prend une geometrie de type GeoJSON en paramètre d'entrée et renvoie les informations intersectant cette géométrie 64 | Prend la source de donnée WFS Géoportail à interroger en paramètre d'entrée 65 | 66 | parameters: 67 | - name: source 68 | in: query 69 | description: Source des données WFS Géoportail 70 | required: true 71 | schema: 72 | type: string 73 | 74 | - name: geom 75 | in: query 76 | required: true 77 | schema: 78 | $ref: "#/components/schemas/Geometry" 79 | 80 | - name: _limit 81 | in: query 82 | description: Limite de résultats à afficher (chiffre entre 1 et 1000) 83 | required: false 84 | schema: 85 | type: integer 86 | 87 | - name: _start 88 | in: query 89 | description: Position pour le début de la recherche 90 | required: false 91 | schema: 92 | type: integer 93 | tags: 94 | - Geoportail 95 | responses: 96 | '200': 97 | description: "Succès" 98 | 99 | components: 100 | schemas: 101 | Coordinate: 102 | type: array 103 | items: 104 | type: number 105 | minItems: 2 106 | maxItems: 4 107 | Geometry: 108 | type: string 109 | description: Une `Geometry` au sens GeoJSON 110 | properties: 111 | type: 112 | type: string 113 | description: Le type géométrique 114 | enum: 115 | - Point 116 | - LineString 117 | - Polygon 118 | - MultiPoint 119 | - MultiLineString 120 | - MultiPolygon 121 | coordinates: 122 | type: array 123 | items: {} 124 | Feature: 125 | type: object 126 | description: Une `Feature` au sens GeoJSON 127 | properties: 128 | type: 129 | type: string 130 | description: Le type d'objet GeoJSON (`Feature`) 131 | id: 132 | type: string 133 | geometry: 134 | $ref: '#/components/schemas/Geometry' 135 | FeatureCollection: 136 | type: object 137 | description: Une `FeatureCollection` au sens GeoJSON 138 | properties: 139 | type: 140 | type: string 141 | description: Le type d'objet GeoJSON (`FeatureCollection`) 142 | features: 143 | type: array 144 | items: {} 145 | MultiPolygon: 146 | type: object 147 | description: Un `MultiPolygon` au sens GeoJSON 148 | properties: 149 | type: 150 | type: string 151 | description: Le type géométrique (`MultiPolygon`) 152 | coordinates: 153 | type: array 154 | items: {} 155 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | api: 4 | build: 5 | context: . 6 | args: 7 | - http_proxy 8 | - https_proxy 9 | - HTTP_PROXY 10 | - HTTPS_PROXY 11 | networks: 12 | - apicarto 13 | environment: 14 | - PGPASSWORD=${POSTGRES_PASSWORD} 15 | - HTTP_PROXY 16 | - HTTPS_PROXY 17 | ports: 18 | # pour http://localhost:8091/api/doc si traefik non installé 19 | - "8091:8091" 20 | depends_on: 21 | - db 22 | labels: 23 | - "traefik.enable=true" 24 | - "traefik.http.routers.apicarto.rule=Host(`apicarto.${HOST_HOSTNAME}`)" 25 | restart: unless-stopped 26 | 27 | db: 28 | container_name: apicarto-db 29 | image: postgis/postgis:13-3.2-alpine 30 | environment: 31 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 32 | networks: 33 | - apicarto 34 | volumes: 35 | - db-data:/var/lib/postgresql/data 36 | restart: unless-stopped 37 | 38 | volumes: 39 | db-data: 40 | 41 | networks: 42 | apicarto: 43 | -------------------------------------------------------------------------------- /helper/parseInseeCode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import pkg from 'lodash'; 3 | const { _ } = pkg; 4 | const collectionIncludes = _.includes; 5 | 6 | const INVALID_PREFIXES = ['96', '98', '99', '00', '20']; 7 | 8 | var parseInseeCode = function(inseeCode) { 9 | if (inseeCode.length !== 5) throw new Error('INSEE code must have 5 characters'); 10 | 11 | inseeCode = inseeCode.toUpperCase(); 12 | let prefix = inseeCode.substr(0, 2); 13 | let suffix = inseeCode.substr(2, 3); 14 | 15 | // Vérifie que le suffixe (3 derniers caractères) ne contient bien que des chiffres 16 | if (! suffix.match(/[0-9]{3}/)) throw new Error('Invalid INSEE code'); 17 | 18 | // Corse 19 | if (prefix === '2A' || prefix === '2B') { 20 | return { code_dep: prefix, code_com: suffix }; 21 | } 22 | 23 | // Vérifie que le préfixe (2 premiers caractères) ne contient bien que des chiffres (Corse exclus) 24 | if (! prefix.match(/[0-9]{2}/)) throw new Error('Invalid INSEE code'); 25 | 26 | 27 | if (collectionIncludes(INVALID_PREFIXES, prefix)) throw new Error('Invalid INSEE code'); 28 | 29 | return { code_dep: prefix, code_com: suffix }; 30 | 31 | }; 32 | 33 | export default parseInseeCode; -------------------------------------------------------------------------------- /lib/ClientBduni.js: -------------------------------------------------------------------------------- 1 | import httpClient from './httpClient.js'; 2 | import buildBduniCqlFilter from './buildBduniCqlFilter.js'; 3 | 4 | /** 5 | * @classdesc 6 | * WFS access client for the geoportal 7 | * @constructor 8 | */ 9 | var ClientBduni = function (options) { 10 | // should be removed to allow user/password? 11 | this.url = options.url || 'https://data.geopf.fr/wfs/ows'; 12 | this.headers = options.headers || {}; 13 | 14 | }; 15 | 16 | /** 17 | * Get WFS URL 18 | */ 19 | ClientBduni.prototype.getUrl = function () { 20 | return this.url; 21 | }; 22 | 23 | 24 | /** 25 | * @private 26 | * @returns {Object} 27 | */ 28 | ClientBduni.prototype.getDefaultParams = function () { 29 | return { 30 | service: 'WFS', 31 | version: '2.0.0' 32 | }; 33 | }; 34 | 35 | /** 36 | * @private 37 | * @returns {Object} 38 | */ 39 | ClientBduni.prototype.getDefaultHeaders = function () { 40 | return this.headers; 41 | }; 42 | 43 | /** 44 | * Get features for a given type 45 | * 46 | * @param {string} typeName - name of type 47 | * @param {object} params - define cumulative filters (bbox, geom) and to manage the pagination 48 | * @param {number} [params._start=0] index of the first result (STARTINDEX on the WFS) 49 | * @param {number} [params._limit] maximum number of result (COUNT on the WFS) 50 | * @param {object} [params.geom] search geometry intersecting the resulting features. 51 | * @param {string} [defaultCRS="urn:ogc:def:crs:EPSG::4326"] default data CRS (required in cql_filter) 52 | * 53 | * @return {Promise} 54 | */ 55 | ClientBduni.prototype.getFeatures = function (typeName, params) { 56 | params = params || {}; 57 | 58 | var headers = this.getDefaultHeaders(); 59 | headers['Accept'] = 'application/json'; 60 | 61 | /* 62 | * GetFeature params 63 | */ 64 | var queryParams = this.getDefaultParams(); 65 | queryParams['request'] = 'GetFeature'; 66 | queryParams['typename'] = typeName; 67 | queryParams['outputFormat'] = 'application/json'; 68 | queryParams['srsName'] = 'CRS:84'; 69 | if (typeof params._limit !== 'undefined') { 70 | queryParams['count'] = params._limit; 71 | } 72 | if (typeof params._start !== 'undefined') { 73 | queryParams['startIndex'] = params._start; 74 | } 75 | 76 | var cql_filter = buildBduniCqlFilter(params); 77 | var body = (cql_filter !== null) ? 'cql_filter=' + encodeURI(cql_filter) : ''; 78 | return httpClient.post(this.getUrl(), body, { 79 | params: queryParams, 80 | headers: headers, 81 | responseType: 'text', 82 | transformResponse: function (body) { 83 | try { 84 | return JSON.parse(body); 85 | } catch (err) { 86 | // forward xml errors 87 | throw { 88 | 'type': 'error', 89 | 'message': body 90 | }; 91 | } 92 | } 93 | }).then(function (response) { 94 | return response.data; 95 | }); 96 | }; 97 | 98 | export {ClientBduni}; 99 | -------------------------------------------------------------------------------- /lib/ClientEr.js: -------------------------------------------------------------------------------- 1 | import httpClient from './httpClient.js'; 2 | import buildErCqlFilter from './buildErCqlFilter.js'; 3 | 4 | /** 5 | * @classdesc 6 | * WFS access client for the geoportal 7 | * @constructor 8 | */ 9 | var ClientEr = function (options) { 10 | // should be removed to allow user/password? 11 | this.url = options.url || 'https://data.geopf.fr/wfs/ows'; 12 | this.headers = options.headers || {}; 13 | 14 | }; 15 | 16 | /** 17 | * Get WFS URL 18 | */ 19 | ClientEr.prototype.getUrl = function () { 20 | return this.url; 21 | }; 22 | 23 | 24 | /** 25 | * @private 26 | * @returns {Object} 27 | */ 28 | ClientEr.prototype.getDefaultParams = function () { 29 | return { 30 | service: 'WFS', 31 | version: '2.0.0' 32 | }; 33 | }; 34 | 35 | /** 36 | * @private 37 | * @returns {Object} 38 | */ 39 | ClientEr.prototype.getDefaultHeaders = function () { 40 | return this.headers; 41 | }; 42 | 43 | /** 44 | * Get features for a given type 45 | * 46 | * @param {string} typeName - name of type 47 | * @param {object} params - define cumulative filters (bbox, geom) and to manage the pagination 48 | * @param {number} [params._start=0] index of the first result (STARTINDEX on the WFS) 49 | * @param {number} [params._limit] maximum number of result (COUNT on the WFS) 50 | * @param {array} [params._propertyNames] restrict a GetFeature request by properties 51 | * @param {object} [params.geom] search geometry intersecting the resulting features. 52 | * @param {object} [params.bbox] search bbox intersecting the resulting features. 53 | * @param {string} [defaultGeomFieldName="the_geom"] name of the geometry column by default 54 | * @param {string} [defaultCRS="urn:ogc:def:crs:EPSG::4326"] default data CRS (required in cql_filter) 55 | * 56 | * @return {Promise} 57 | */ 58 | ClientEr.prototype.getFeatures = function (typeName, params) { 59 | params = params || {}; 60 | 61 | var headers = this.getDefaultHeaders(); 62 | headers['Accept'] = 'application/json'; 63 | 64 | /* 65 | * GetFeature params 66 | */ 67 | var queryParams = this.getDefaultParams(); 68 | queryParams['request'] = 'GetFeature'; 69 | queryParams['typename'] = typeName; 70 | queryParams['outputFormat'] = 'application/json'; 71 | queryParams['srsName'] = 'CRS:84'; 72 | if (typeof params._limit !== 'undefined') { 73 | queryParams['count'] = params._limit; 74 | } 75 | if (typeof params._start !== 'undefined') { 76 | queryParams['startIndex'] = params._start; 77 | } 78 | 79 | if (typeof params._propertyNames !== 'undefined') { 80 | queryParams['propertyName'] = params._propertyNames.join(); 81 | } 82 | /* 83 | * bbox and attribute filter as POST parameter 84 | */ 85 | var cql_filter = buildErCqlFilter(params); 86 | var body = (cql_filter !== null) ? 'cql_filter=' + encodeURI(cql_filter) : ''; 87 | return httpClient.post(this.getUrl(), body, { 88 | params: queryParams, 89 | headers: headers, 90 | responseType: 'text', 91 | transformResponse: function (body) { 92 | try { 93 | return JSON.parse(body); 94 | } catch (err) { 95 | // forward xml errors 96 | throw { 97 | 'type': 'error', 98 | 'message': body 99 | }; 100 | } 101 | } 102 | }).then(function (response) { 103 | return response.data; 104 | }); 105 | }; 106 | 107 | export {ClientEr}; 108 | -------------------------------------------------------------------------------- /lib/buildBduniCqlFilter.js: -------------------------------------------------------------------------------- 1 | import { geojsonToWKT } from '@terraformer/wkt'; 2 | import flip from '@turf/flip'; 3 | 4 | /* 5 | * WARNING: Despite the use of WGS84, you need to do a flip on the coordinates 6 | */ 7 | 8 | /** 9 | * Build cql_filter parameter for GeoServer according to user params. 10 | * 11 | * @param {object} params 12 | * @param {object} [params.geom] search geometry intersecting the resulting features. 13 | * @param {number} [distance=100] rayon de recherche par défaut 14 | * @param {string} [geomDefaultCRS=constants.defaultCRS="urn:ogc:def:crs:EPSG::4326"] default data CRS (required in cql_filter) 15 | * @returns {string} 16 | */ 17 | function buildBduniCqlFilter(params) { 18 | 19 | var parts = [] ; 20 | for ( var name in params ){ 21 | // ignore _limit, _start, etc. 22 | if ( name.charAt(0) === '_' || name === 'distance'){ 23 | continue; 24 | } 25 | 26 | if ( name == 'geom' ){ 27 | var geom = params[name] ; 28 | if ( typeof geom !== 'object' ){ 29 | geom = JSON.parse(geom) ; 30 | } 31 | var wkt = geojsonToWKT(flip(geom)); 32 | parts.push('DWITHIN(geometrie,' + wkt + ', ' + params.distance + ', meters)'); 33 | }else { 34 | parts.push(name+'=\''+ params[name]+'\''); 35 | } 36 | } 37 | if ( parts.length === 0 ){ 38 | return null; 39 | } 40 | return parts.join(' and ') ; 41 | } 42 | 43 | export default buildBduniCqlFilter; 44 | -------------------------------------------------------------------------------- /lib/buildErCqlFilter.js: -------------------------------------------------------------------------------- 1 | import { geojsonToWKT } from '@terraformer/wkt'; 2 | import flip from '@turf/flip'; 3 | 4 | /* 5 | * WARNING: Despite the use of WGS84, you need to do a flip on the coordinates 6 | */ 7 | 8 | /** 9 | * Convert a bbox on array with 4 values 10 | */ 11 | function parseBoundingBox(bbox){ 12 | if ( typeof bbox !== 'string' ){ 13 | return bbox; 14 | } 15 | return bbox.replace(/'/g, '').split(','); 16 | } 17 | 18 | /** 19 | * Convert a bbox in cql_filter fragment 20 | */ 21 | function bboxToFilter(bbox){ 22 | bbox = parseBoundingBox(bbox); 23 | var xmin = bbox[1]; 24 | var ymin = bbox[0]; 25 | var xmax = bbox[3]; 26 | var ymax = bbox[2]; 27 | return 'BBOX(the_geom,'+xmin+','+ymin+','+xmax+','+ymax+')' ; 28 | } 29 | 30 | /** 31 | * Build cql_filter parameter for GeoServer according to user params. 32 | * 33 | * @param {object} params 34 | * @param {object} [params.geom] search geometry intersecting the resulting features. 35 | * @param {object} [params.bbox] search bbox intersecting the resulting features. 36 | * @param {string} [geomFieldName="the_geom"] name of the geometry column by default 37 | * @param {string} [geomDefaultCRS=constants.defaultCRS="urn:ogc:def:crs:EPSG::4326"] default data CRS (required in cql_filter) 38 | * @returns {string} 39 | */ 40 | function buildErCqlFilter(params) { 41 | 42 | var parts = [] ; 43 | for ( var name in params ){ 44 | // ignore _limit, _start, etc. 45 | if ( name.charAt(0) === '_' ){ 46 | continue; 47 | } 48 | 49 | if ( name == 'bbox' ){ 50 | parts.push(bboxToFilter(params['bbox'])) ; 51 | }else if ( name == 'geom' ){ 52 | var geom = params[name] ; 53 | if ( typeof geom !== 'object' ){ 54 | geom = JSON.parse(geom) ; 55 | } 56 | var wkt = geojsonToWKT(flip(geom)); 57 | parts.push('INTERSECTS(the_geom,'+wkt+')'); 58 | 59 | // Field_date sous la forme date_maj_deb;date_maj_fin, nous devons donc les separer pour la requete 60 | } else if (name == 'field_date'){ 61 | var value_date = params[name].split(';'); 62 | parts.push('updated_at AFTER '+ value_date[0]+' AND updated_at BEFORE '+ value_date[1]); 63 | 64 | } else if (name == 'field_publication_date'){ 65 | var value_date_publication = params[name].split(';'); 66 | parts.push('publication_date AFTER '+ value_date_publication[0]+' AND publication_date BEFORE '+ value_date_publication[1]); 67 | 68 | } else if (name == 'field_depublication_date'){ 69 | var value_date_depublication = params[name].split(';'); 70 | parts.push('deleted_at AFTER '+ value_date_depublication[0]+' AND deleted_at BEFORE '+ value_date_depublication[1]); 71 | 72 | } else if (name == 'namepr') { //Traiter le cas du produit avec le parametre name 73 | parts.push(' name ILIKE \'%'+ params[name] + '%\' OR name_complement ILIKE \'%'+ params[name] + '%\''); 74 | 75 | } else if((name == 'has_geometry') && (params[name] ==false)){ 76 | continue; //We do nothing when value has geometry = false to get all results. 77 | 78 | // Search in category 79 | } else if (name == 'category_id') { 80 | var valueCat = params[name].split(';'); 81 | if(valueCat.length < 2) { 82 | parts.push(name+'=\''+ params[name]+'\''); 83 | } else { 84 | var chaineCategoryId = ''; 85 | var chaineConnector = ''; 86 | for (let i = 0; i < valueCat.length; i++) { 87 | if(i == 0) { chaineConnector =''; } else { chaineConnector = ' OR ';} 88 | chaineCategoryId = chaineCategoryId + chaineConnector + ' category_id='+valueCat[i]; 89 | } 90 | parts.push(chaineCategoryId); 91 | } 92 | } else { 93 | parts.push(name+'=\''+ params[name]+'\''); 94 | } 95 | } 96 | if ( parts.length === 0 ){ 97 | return null; 98 | } 99 | return parts.join(' and ') ; 100 | } 101 | 102 | export default buildErCqlFilter; 103 | -------------------------------------------------------------------------------- /lib/cql_filter.js: -------------------------------------------------------------------------------- 1 | import { geojsonToWKT } from '@terraformer/wkt'; 2 | import flip from '@turf/flip'; 3 | 4 | /* 5 | * WARNING: Despite the use of WGS84, you need to do a flip on the coordinates 6 | */ 7 | 8 | /** 9 | * Convert a bbox on array with 4 values 10 | */ 11 | function parseBoundingBox(bbox){ 12 | if ( typeof bbox !== 'string' ){ 13 | return bbox; 14 | } 15 | return bbox.replace(/'/g, '').split(','); 16 | } 17 | 18 | /** 19 | * Convert a bbox in cql_filter fragment 20 | */ 21 | function bboxToFilter(bbox){ 22 | bbox = parseBoundingBox(bbox); 23 | var xmin = bbox[1]; 24 | var ymin = bbox[0]; 25 | var xmax = bbox[3]; 26 | var ymax = bbox[2]; 27 | return 'BBOX(the_geom,'+xmin+','+ymin+','+xmax+','+ymax+')' ; 28 | } 29 | 30 | var cqlFilter = function(params){ 31 | var parts = [] ; 32 | for ( var name in params ){ 33 | // ignore _limit, _start, etc. 34 | if ( name.charAt(0) === '_' ){ 35 | continue; 36 | } 37 | 38 | if ( name == 'bbox' ){ 39 | parts.push(bboxToFilter(params['bbox'])) ; 40 | }else if ( name == 'geom' ){ 41 | var geom = params[name] ; 42 | if ( typeof geom !== 'object' ){ 43 | geom = JSON.parse(geom) ; 44 | } 45 | var wkt = geojsonToWKT(flip(geom)); 46 | parts.push('INTERSECTS(geom,'+wkt+')'); 47 | }else{ 48 | parts.push(name+'=\''+ params[name]+'\''); 49 | } 50 | } 51 | if ( parts.length === 0 ){ 52 | return null; 53 | } 54 | return parts.join(' and ') ; 55 | }; 56 | 57 | export default cqlFilter; 58 | -------------------------------------------------------------------------------- /lib/httpClient.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { HttpProxyAgent } from 'http-proxy-agent'; 3 | import { HttpsProxyAgent } from 'https-proxy-agent'; 4 | 5 | const axiosGlobalConfig = {}; 6 | 7 | /* 8 | * NodeJS specific code to allow https throw http proxy with axios 9 | * (fixes https://github.com/IGNF/geoportal-wfs-client/issues/5) 10 | */ 11 | if (typeof window === 'undefined' ){ 12 | if ( process.env.HTTP_PROXY ){ 13 | axiosGlobalConfig.httpAgent = new HttpProxyAgent(process.env.HTTP_PROXY); 14 | } 15 | if ( process.env.HTTPS_PROXY ){ 16 | axiosGlobalConfig.httpsAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); 17 | } 18 | axiosGlobalConfig.proxy = false; 19 | } 20 | 21 | const httpClient = axios.create(axiosGlobalConfig); 22 | 23 | export default httpClient; 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /middlewares/aocWfsClient.js: -------------------------------------------------------------------------------- 1 | import GeoportalWfsClient from 'geoportal-wfs-client'; 2 | 3 | /* 4 | * Middleware pour la création du client WFS geoportail 5 | * 6 | */ 7 | var aocWfsClient = function(req, res, next) { 8 | /* gestion des variables d'environnement et valeur par défaut */ 9 | var options = { 10 | 'defaultGeomFieldName': 'geom', 11 | url: 'https://data.geopf.fr/private/wfs', 12 | headers:{ 13 | 'User-Agent': 'apicarto', 14 | 'Referer': 'http://localhost' 15 | } 16 | }; 17 | 18 | if ( req.headers.referer ){ 19 | options.headers.Referer = req.headers.referer ; 20 | } 21 | if ( process.env.GEOPORTAL_REFERER ){ 22 | options.headers.Referer = process.env.GEOPORTAL_REFERER ; 23 | } 24 | 25 | req.aocWfsClient = new GeoportalWfsClient(options); 26 | 27 | next(); 28 | }; 29 | 30 | export default aocWfsClient; -------------------------------------------------------------------------------- /middlewares/bduniWfsClient.js: -------------------------------------------------------------------------------- 1 | import { ClientBduni as GeoportalWfsClientBduni } from '../lib/ClientBduni.js'; 2 | 3 | 4 | /* 5 | * Middleware pour la création du client WFS geoportail 6 | * 7 | * TODO permettre la définition de la clé au niveau du serveur 8 | */ 9 | var bduniWfsClient = function(req, res, next) { 10 | /* gestion des variables d'environnement et valeur par défaut */ 11 | var options = { 12 | url: 'https://data.geopf.fr/wfs/ows', 13 | headers:{ 14 | 'User-Agent': 'apicarto', 15 | 'Referer': 'http://localhost' 16 | } 17 | }; 18 | 19 | /* gestion du paramètre Referer */ 20 | if ( req.headers.referer ){ 21 | options.headers.Referer = req.headers.referer ; 22 | } 23 | req.bduniWfsClient = new GeoportalWfsClientBduni(options); 24 | 25 | next(); 26 | }; 27 | 28 | export default bduniWfsClient; -------------------------------------------------------------------------------- /middlewares/drealCorseWfsClient.js: -------------------------------------------------------------------------------- 1 | import GeoportalWfsClient from 'geoportal-wfs-client'; 2 | 3 | /* 4 | * middleware pour la création du client geoportail 5 | */ 6 | var drealCorseWfsClient = function(req, res, next) { 7 | var referer = 'http://localhost'; 8 | 9 | /* forward du referer du client */ 10 | if ( req.headers.referer ){ 11 | referer = req.headers.referer ; 12 | } 13 | 14 | req.drealCorseWfsClient = new GeoportalWfsClient({ 15 | 'defaultGeomFieldName': 'geom', 16 | 'apiKey': 'geoserver', 17 | 'url' : 'https://georchestra.ac-corse.fr/{apiKey}/wfs', 18 | 'headers':{ 19 | Referer: referer, 20 | 'User-Agent': 'apicarto' 21 | } 22 | }); 23 | next(); 24 | }; 25 | 26 | export default drealCorseWfsClient; -------------------------------------------------------------------------------- /middlewares/erWfsClient.js: -------------------------------------------------------------------------------- 1 | import { ClientEr as GeoportalWfsClientEr } from '../lib/ClientEr.js'; 2 | 3 | 4 | /* 5 | * Middleware pour la création du client WFS geoportail 6 | * 7 | * TODO permettre la définition de la clé au niveau du serveur 8 | */ 9 | var erWfsClient = function(req, res, next) { 10 | /* gestion des variables d'environnement et valeur par défaut */ 11 | var options = { 12 | url: 'https://data.geopf.fr/wfs/ows', 13 | headers:{ 14 | 'User-Agent': 'apicarto', 15 | 'Referer': 'http://localhost' 16 | } 17 | }; 18 | 19 | /* gestion du paramètre Referer */ 20 | if ( req.headers.referer ){ 21 | options.headers.Referer = req.headers.referer ; 22 | } 23 | req.erWfsClient = new GeoportalWfsClientEr(options); 24 | 25 | next(); 26 | }; 27 | 28 | export default erWfsClient; -------------------------------------------------------------------------------- /middlewares/gppWfsClient.js: -------------------------------------------------------------------------------- 1 | import GeoportalWfsClient from 'geoportal-wfs-client'; 2 | 3 | /* 4 | * Middleware pour la création du client WFS geoportail 5 | * 6 | */ 7 | var gppWfsClient = function(req, res, next) { 8 | /* gestion des variables d'environnement et valeur par défaut */ 9 | var options = { 10 | 'defaultGeomFieldName': 'geom', 11 | url: 'https://data.geopf.fr/wfs/ows', 12 | headers:{ 13 | 'User-Agent': 'apicarto', 14 | 'Referer': 'http://localhost' 15 | } 16 | }; 17 | 18 | if ( req.headers.referer ){ 19 | options.headers.Referer = req.headers.referer ; 20 | } 21 | if ( process.env.GEOPORTAL_REFERER ){ 22 | options.headers.Referer = process.env.GEOPORTAL_REFERER ; 23 | } 24 | 25 | req.gppWfsClient = new GeoportalWfsClient(options); 26 | 27 | next(); 28 | }; 29 | 30 | export default gppWfsClient; -------------------------------------------------------------------------------- /middlewares/gpuWfsClient.js: -------------------------------------------------------------------------------- 1 | import GeoportalWfsClient from 'geoportal-wfs-client'; 2 | /* 3 | * middleware pour la création du client geoportail 4 | */ 5 | var gpuWfsClient = function (req, res, next) { 6 | var referer = 'http://localhost'; 7 | 8 | /* forward du referer du client */ 9 | if ( req.headers.referer ){ 10 | referer = req.headers.referer ; 11 | } 12 | 13 | req.gpuWfsClient = new GeoportalWfsClient({ 14 | 'defaultGeomFieldName': 'the_geom', 15 | url: 'https://data.geopf.fr/wfs/ows', 16 | 'headers':{ 17 | Referer: referer, 18 | 'User-Agent': 'apicarto' 19 | } 20 | }); 21 | next(); 22 | }; 23 | 24 | export default gpuWfsClient; -------------------------------------------------------------------------------- /middlewares/naturegppWfsClient.js: -------------------------------------------------------------------------------- 1 | import GeoportalWfsClient from 'geoportal-wfs-client'; 2 | 3 | 4 | /* 5 | * Middleware pour la création du client WFS geoportail 6 | * 7 | * TODO permettre la définition de la clé au niveau du serveur 8 | */ 9 | var gppWfsClient = function(req, res, next) { 10 | /* gestion des variables d'environnement et valeur par défaut */ 11 | var options = { 12 | 'defaultCRS': 'EPSG:3857', 13 | 'defaultGeomFieldName': 'geom', 14 | url: 'https://data.geopf.fr/wfs/ows', 15 | headers:{ 16 | 'User-Agent': 'apicarto', 17 | 'Referer': 'http://localhost' 18 | } 19 | }; 20 | 21 | /* gestion du paramètre Referer */ 22 | if ( req.headers.referer ){ 23 | options.headers.Referer = req.headers.referer ; 24 | } 25 | 26 | req.gppWfsClient = new GeoportalWfsClient(options); 27 | 28 | next(); 29 | }; 30 | 31 | export default gppWfsClient; -------------------------------------------------------------------------------- /middlewares/pgClient.js: -------------------------------------------------------------------------------- 1 | import createDebugMessages from 'debug'; 2 | import Client from 'pg'; 3 | 4 | const debug = createDebugMessages('apicarto'); 5 | 6 | /* 7 | * middleware pour la création et la libération des connexions postgresql 8 | */ 9 | var pgClient = function(req, res, next) { 10 | debug('create pg connection...'); 11 | req.pgClient = new Client.Client({ 12 | user: process.env.PGUSER|| 'postgis', 13 | host: process.env.PGHOST || 'localhost', 14 | database: process.env.PGDATABASE || 'postgres', 15 | password: process.env.PGPASSWORD || 'postgis', 16 | port: process.env.PGPORT || '5432' 17 | }); 18 | req.pgClient.connect().then(function(){ 19 | var _end = res.end; 20 | res.end = function(){ 21 | debug('close connection...'); 22 | req.pgClient.end(); 23 | _end.apply(this, arguments); 24 | }; 25 | next(); 26 | }).catch(function(err){ 27 | res.status(500).json({ 28 | 'code': 500, 29 | 'message': 'Erreur de la connexion à la base de données' 30 | }); 31 | debug(err); 32 | }); 33 | }; 34 | 35 | export default pgClient; -------------------------------------------------------------------------------- /middlewares/request-logger.js: -------------------------------------------------------------------------------- 1 | import { createLogger } from 'bunyan'; 2 | import { v4 as uuidV4} from 'uuid'; 3 | import onFinished from 'on-finished'; 4 | import _ from 'lodash'; 5 | 6 | var requestLogger = function () { 7 | const logger = createLogger({ 8 | name: 'request' 9 | }); 10 | 11 | return function (req, res, next) { 12 | // Start request duration counter 13 | const startTime = Date.now(); 14 | 15 | // Define request id 16 | req.reqId = uuidV4(); 17 | 18 | const payload = { 19 | reqId: req.reqId, 20 | method: req.method, 21 | path: req.path, 22 | agent: req.headers['user-agent'], 23 | remote: req.ip || (req.connection && req.connection.remoteAddress) 24 | }; 25 | 26 | // Query string 27 | const query = _.omit(req.query, 'geom'); 28 | if (req.query.geom) query.geom = 'provided'; 29 | if (Object.keys(query).length > 0) payload.query = query; 30 | 31 | onFinished(res, function (err, res) { 32 | payload.code = res.statusCode; 33 | payload.duration = Date.now() - startTime; 34 | if (res.getHeader('content-length')) payload.size = res.getHeader('content-length'); 35 | logger.info(payload); 36 | }); 37 | 38 | next(); 39 | }; 40 | }; 41 | 42 | export {requestLogger}; -------------------------------------------------------------------------------- /middlewares/ressources_cle_wfs2022-05-20.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IGNF/apicarto/bf8f771d3d6d55e6b44d06ef7c80f2b566641dfa/middlewares/ressources_cle_wfs2022-05-20.csv -------------------------------------------------------------------------------- /middlewares/validateParams.js: -------------------------------------------------------------------------------- 1 | import { validationResult } from 'express-validator'; 2 | 3 | /** 4 | * 5 | * Middleware de validation des paramètres s'appuyant sur express-validator et uniformisant 6 | * les retours d'erreur dans l'API. 7 | * 8 | * @param {Object} req 9 | * @param {Object} res 10 | */ 11 | var validateParams = function(req,res,next){ 12 | const errors = validationResult(req); 13 | if (!errors.isEmpty()) { 14 | return res.status(400).json({ 15 | 'code': 400, 16 | 'message': errors.mapped() 17 | }); 18 | } 19 | next(); 20 | }; 21 | 22 | export default validateParams; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apicarto", 3 | "version": "v2.9.0", 4 | "main": "index.js", 5 | "type": "module", 6 | "scripts": { 7 | "test": "npm run lint && npm run test-unit", 8 | "test-unit": "c8 --reporter=html --reporter=text --reporter=cobertura mocha", 9 | "lint": "eslint -c .eslint.config.js controllers/**/*.js middlewares/*.js helper/**/*.js *.js test/**/*.js lib/**/*.js", 10 | "start": "node server | bunyan", 11 | "coveralls": "c8 report --reporter=text-lcov | coveralls" 12 | }, 13 | "contributors": [ 14 | "Jérôme Desboeufs ", 15 | "Nabil Servais ", 16 | "Vincent Sagniez ", 17 | "Mickael Borne ", 18 | "Manuel Frangi " 19 | ], 20 | "repository": "IGNF/apicarto", 21 | "license": "CECILL-B", 22 | "engines": { 23 | "node": ">=20" 24 | }, 25 | "//": "La dépendance 'nomnom' provoque une vulnérabilité critique si la version est supérieure à 1.5.3", 26 | "dependencies": { 27 | "@mapbox/geojsonhint": "^3.3.0", 28 | "@terraformer/wkt": "^2.2.1", 29 | "@turf/turf": "^7.2.0", 30 | "axios": "^1.8.2", 31 | "bunyan": "^1.8.15", 32 | "codes-postaux": "^4.1.2", 33 | "cors": "^2.8.5", 34 | "debug": "^4.4.0", 35 | "ejs": "^3.1.10", 36 | "express": "^4.21.2", 37 | "express-validator": "^7.2.1", 38 | "geoportal-wfs-client": "https://github.com/IGNF/geoportal-wfs-client#v1.0.3", 39 | "handlebars": "^4.7.8", 40 | "http-proxy-agent": "^7.0.2", 41 | "https-proxy-agent": "^7.0.6", 42 | "jsts": "^2.12.1", 43 | "lodash": "^4.17.21", 44 | "node-cache": "^5.1.2", 45 | "nomnom": "1.5.3", 46 | "pg": "^8.13.3", 47 | "pg-format": "^1.0.4", 48 | "shelljs": "^0.9.1", 49 | "swagger-ui-dist": "^5.20.1", 50 | "uuid": "^11.1.0" 51 | }, 52 | "devDependencies": { 53 | "c8": "^10.1.3", 54 | "coveralls": "^3.1.1", 55 | "eslint": "^9.22.0", 56 | "expect.js": "^0.3.1", 57 | "globals": "^16.0.0", 58 | "mocha": "^11.1.0", 59 | "mocha-lcov-reporter": "^1.3.0", 60 | "supertest": "^7.0.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /processes.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [ 3 | { 4 | "name": "apicarto", 5 | "script": "server.js", 6 | "exec_mode": "cluster", 7 | "instances": 1, 8 | "env": { 9 | "PG_URI": "postgres://localhost/apicarto", 10 | "GEOPORTAIL_KEY": "", 11 | "GEOPORTAIL_REFERER": "http://localhost", 12 | "PORT": 8091 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import { app } from './app.js'; 3 | 4 | var port = process.env.PORT || 8091; 5 | app.listen(port, () => { 6 | debug(`apicarto is running on port ${port} (see http://localhost:${port}/api/doc/ )`); 7 | }); 8 | -------------------------------------------------------------------------------- /test/checker/isCodeInsee.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import expect from 'expect.js'; 3 | import isCodeInsee from '../../checker/isCodeInsee.js'; 4 | 5 | const invalidInseeCodes = ['invalid', '25','2A', '99999']; 6 | const validInseeCodes = ['25349', '97501','2A004', '2B033']; 7 | 8 | describe('Test checker.isCodeInsee', function() { 9 | 10 | describe('Test invalid codes', function() { 11 | invalidInseeCodes.forEach(function(code){ 12 | it('should report an error for "'+code+'"', function() { 13 | expect(isCodeInsee).withArgs(code).to.throwError(); 14 | }); 15 | }); 16 | }); 17 | 18 | describe('Test valid codes', function() { 19 | validInseeCodes.forEach(function(code){ 20 | it('should not report an error for "'+code+'", should return true', function() { 21 | expect(isCodeInsee).withArgs(code).to.not.throwError(); 22 | expect(isCodeInsee(code)).to.be.eql(true); 23 | }); 24 | }); 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /test/checker/isGeometry.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | 3 | import expect from 'expect.js'; 4 | import isGeometry from '../../checker/isGeometry.js'; 5 | 6 | describe('Test checker.isGeometry', function() { 7 | describe('with a string', function() { 8 | it('should report an error', function() { 9 | expect(isGeometry).withArgs('test string').to.throwError(); 10 | }); 11 | }); 12 | 13 | describe('with a non geometry object', function() { 14 | it('should report an error', function() { 15 | expect(isGeometry).withArgs({username: 'toto'}).to.throwError(); 16 | }); 17 | }); 18 | 19 | describe('with an invalid Point', function() { 20 | it('should not report an error', function() { 21 | expect(isGeometry).withArgs({type: 'Point',coordinates:[3.0]}).to.throwError(); 22 | }); 23 | }); 24 | 25 | describe('with a valid Point', function() { 26 | it('should not report an error', function() { 27 | expect(isGeometry).withArgs({type: 'Point',coordinates:[3.0,5.0]}).to.not.throwError(); 28 | }); 29 | }); 30 | 31 | describe('with a valid Point but too many decimals', function() { 32 | it('should not report an error', function() { 33 | expect(isGeometry).withArgs({type: 'Point',coordinates:[3.123456789,5.123456789]}).to.not.throwError(); 34 | }); 35 | }); 36 | 37 | describe('with a polygon with a bad orientation', function(){ 38 | it('should not report an error', function(){ 39 | expect(isGeometry).withArgs( 40 | {type:'MultiPolygon',coordinates:[[[[-1.6993786,48.1113366],[-1.6994647,48.1113416],[-1.6994613,48.1113573],[-1.6993639,48.111803],[-1.6992707,48.112222],[-1.6990176,48.1120599],[-1.6989945,48.1120573],[-1.6991084,48.111617],[-1.6991262,48.1115482],[-1.6993407,48.1115608],[-1.6993494,48.1115158],[-1.699361,48.111431],[-1.6993786,48.1113366]]]]} 41 | ).to.not.throwError(); 42 | }); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /test/controllers/bduni/troncon.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('Testing /api/bduni/troncon', function() { 7 | 8 | describe('With invalid inputs', function() { 9 | 10 | describe('With invalid geom', function() { 11 | it('should reply with 400', function(done){ 12 | request(app) 13 | .get('/api/bduni/troncon?geom=not_valid') 14 | .expect(400,done) 15 | ; 16 | }); 17 | }); 18 | 19 | }); 20 | 21 | describe('/api/bduni/troncon?geom={"type":"LineString","coordinates":[[4.681549,47.793784],[4.741974,47.788248]]}',function(){ 22 | it('should reply a FeatureCollection with valid features', done => { 23 | request(app) 24 | .get('/api/bduni/troncon?geom={"type":"LineString","coordinates":[[4.681549,47.793784],[4.741974,47.788248]]}') 25 | .expect(400, done) 26 | }); 27 | }); 28 | 29 | describe('/api/bduni/troncon?geom={"type":"Point","coordinates":[4.829214,45.996981]}',function(){ 30 | it('should reply a FeatureCollection with valid features', done => { 31 | request(app) 32 | .get('/api/bduni/troncon?geom={"type":"Point","coordinates":[4.829214,45.996981]}') 33 | .expect(200) 34 | .expect(res => { 35 | const feature = res.body.features[1]; 36 | expect(feature.geometry.type).to.eql('Point'); 37 | expect(feature.properties).to.eql({ 38 | "cleabs": "TRONROUT0000000009077731", 39 | "cl_admin": "Départementale", 40 | "nature": "Route à 1 chaussée", 41 | "pos_sol": "0", 42 | "importance": "3", 43 | "nb_voies": "2", 44 | "sens": "Double sens", 45 | "largeur": 5.5, 46 | "gestion": "01", 47 | "numero": "D904", 48 | "distance": 12.024405025580256 49 | }); 50 | }) 51 | .end(done); 52 | }); 53 | }); 54 | 55 | describe('/api/bduni/troncon?lon=4.82921&lat=45.996981',function(){ 56 | it('should reply a FeatureCollection with valid features', done => { 57 | request(app) 58 | .get('/api/bduni/troncon?lon=4.829214&lat=45.996981') 59 | .expect(200) 60 | .expect(res => { 61 | const feature = res.body.features[1]; 62 | expect(feature.geometry.type).to.eql('Point'); 63 | expect(feature.properties).to.eql({ 64 | "cleabs": "TRONROUT0000000009077731", 65 | "cl_admin": "Départementale", 66 | "nature": "Route à 1 chaussée", 67 | "pos_sol": "0", 68 | "importance": "3", 69 | "nb_voies": "2", 70 | "sens": "Double sens", 71 | "largeur": 5.5, 72 | "gestion": "01", 73 | "numero": "D904", 74 | "distance": 12.024405025580256 75 | }); 76 | }) 77 | .end(done); 78 | }); 79 | }); 80 | 81 | describe('/api/bduni/troncon?lon=4.829214&lat=45.996981&distance=100',function(){ 82 | it('should reply a FeatureCollection with valid features', done => { 83 | request(app) 84 | .get('/api/bduni/troncon?lon=4.829214&lat=45.996981&distance=100') 85 | .expect(200) 86 | .expect(res => { 87 | const feature = res.body.features[1]; 88 | expect(feature.geometry.type).to.eql('Point'); 89 | expect(feature.properties).to.eql({ 90 | "cleabs": "TRONROUT0000000009077731", 91 | "cl_admin": "Départementale", 92 | "nature": "Route à 1 chaussée", 93 | "pos_sol": "0", 94 | "importance": "3", 95 | "nb_voies": "2", 96 | "sens": "Double sens", 97 | "largeur": 5.5, 98 | "gestion": "01", 99 | "numero": "D904", 100 | "distance": 12.024405025580256 101 | }); 102 | }) 103 | .end(done); 104 | }); 105 | }); 106 | 107 | 108 | }); 109 | -------------------------------------------------------------------------------- /test/controllers/cadastre/commune.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('Testing /api/cadastre/commune', function() { 7 | 8 | describe('With invalid inputs', function() { 9 | 10 | describe('With invalid code_insee', function() { 11 | it('should reply with 400', function(done){ 12 | request(app) 13 | .get('/api/cadastre/commune?code_insee=not_valid') 14 | .expect(400,done) 15 | ; 16 | }); 17 | }); 18 | 19 | }); 20 | 21 | /* filtrage par code insee */ 22 | describe('/api/cadastre/commune?code_insee=55001',function(){ 23 | it('should reply with 200', done => { 24 | request(app) 25 | .get('/api/cadastre/commune?code_insee=55001') 26 | .expect(res => { 27 | const feature = res.body.features[0]; 28 | expect(feature.geometry.type).to.eql("MultiPolygon"); 29 | /*expect(feature.properties).to.eql({ 30 | nom_com: 'Abainville', 31 | code_dep: '55', 32 | code_insee: '55001' 33 | });*/ 34 | }) 35 | .end(done); 36 | }); 37 | }); 38 | 39 | /* filtrage par code_dep */ 40 | describe('/api/cadastre/commune?code_dep=94',function(){ 41 | it('should reply a FeatureCollection with valid features', done => { 42 | request(app) 43 | .get('/api/cadastre/commune?code_dep=94') 44 | .expect(200) 45 | .expect(res => { 46 | const features = res.body.features; 47 | expect(features.length).to.be.greaterThan(5); 48 | }) 49 | .end(done) 50 | ; 51 | }); 52 | }); 53 | 54 | describe('/api/cadastre/commune?geom={"type":"Point","coordinates":[4.7962,45.22456]}',function(){ 55 | it('should reply a FeatureCollection with valid features', done => { 56 | request(app) 57 | .post('/api/cadastre/commune') 58 | .expect(200) 59 | .send({ 'geom': {"type":"Point","coordinates":[4.7962,45.22456]}}) 60 | .expect(res => { 61 | const feature = res.body.features[0]; 62 | expect(feature.geometry.type).to.eql('MultiPolygon'); 63 | expect(feature.properties).to.eql({ 64 | "gid": 28977, 65 | "nom_com": "Andance", 66 | "code_dep": "07", 67 | "code_insee": "07009", 68 | }); 69 | expect(feature.bbox).to.eql( 70 | [ 71 | 4.78197598, 72 | 45.20282918, 73 | 4.81150389, 74 | 45.26006379 75 | ] 76 | ); 77 | }) 78 | .end(done); 79 | }); 80 | }); 81 | 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /test/controllers/cadastre/division.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | const EXPECTED_PROPERTIES = [ 7 | "code_arr", 8 | "code_com", 9 | "code_dep", 10 | "code_insee", 11 | "com_abs", 12 | "echelle", 13 | "edition", 14 | "feuille", 15 | "nom_com", 16 | "section" 17 | ]; 18 | 19 | 20 | describe('Testing /api/cadastre/division', function() { 21 | 22 | describe('With invalid inputs', function() { 23 | 24 | describe('With invalid code_insee', function() { 25 | it('should reply with 400 for insee=testapi', function(done){ 26 | request(app) 27 | .get('/api/cadastre/division?code_insee=testapi') 28 | .expect(400,done); 29 | }); 30 | }); 31 | 32 | describe('With invalid section', function() { 33 | it('should reply with 400', function(done){ 34 | request(app) 35 | .get('/api/cadastre/division?code_insee=94067§ion=invalid') 36 | .expect(400,done); 37 | }); 38 | }); 39 | 40 | }); 41 | 42 | describe('/api/cadastre/division?code_insee=94067§ion=0A', function() { 43 | it('should reply a FeatureCollection containing a valid Feature for insee=94067 and section=0A', done => { 44 | request(app) 45 | .get('/api/cadastre/division?code_insee=94067§ion=0A') 46 | .expect(200) 47 | .expect(res => { 48 | const feature = res.body.features[0]; 49 | expect(feature.geometry.type).to.eql('MultiPolygon'); 50 | let propertyNames = Object.keys(feature.properties); 51 | propertyNames.sort(); 52 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 53 | expect(feature.properties.nom_com).to.eql('Saint-Mandé'); 54 | }) 55 | .end(done); 56 | }); 57 | }); 58 | 59 | describe('/api/cadastre/division?code_insee=75056&code_arr=112§ion=AA', function() { 60 | it('should reply a FeatureCollection containing a valid Feature for case Paris 12e', done => { 61 | request(app) 62 | .get('/api/cadastre/division?code_insee=75056&code_arr=112§ion=AA') 63 | .expect(200) 64 | .expect(res => { 65 | const feature = res.body.features[0]; 66 | expect(feature.geometry.type).to.eql('MultiPolygon'); 67 | let propertyNames = Object.keys(feature.properties); 68 | propertyNames.sort(); 69 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 70 | 71 | expect(feature.properties.feuille).to.eql(1); 72 | expect(feature.properties.section).to.eql('AA'); 73 | expect(feature.properties.code_dep).to.eql('75'); 74 | expect(feature.properties.nom_com).to.eql('Paris'); 75 | expect(feature.properties.code_com).to.eql('056'); 76 | expect(feature.properties.com_abs).to.eql('000'); 77 | expect(feature.properties.echelle).to.eql('500'); 78 | expect(feature.properties.edition).to.eql('1'); 79 | expect(feature.properties.code_arr).to.eql('112'); 80 | // bbox: [2.39897142,48.84503372,2.40345206,48.84809772] 81 | expect(feature.properties.code_insee).to.eql('75056'); 82 | }) 83 | .end(done); 84 | }); 85 | }); 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /test/controllers/cadastre/localisant.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('Testing /api/cadastre/localisant', function() { 7 | 8 | describe('With invalid inputs', function() { 9 | 10 | describe('With invalid code_insee', function() { 11 | it('should reply with 400 for insee=testapi', function(done){ 12 | request(app) 13 | .get('/api/cadastre/localisant?code_insee=testapi') 14 | .expect(400,done); 15 | }); 16 | }); 17 | 18 | describe('With invalid section', function() { 19 | it('should reply with 400', function(done){ 20 | request(app) 21 | .get('/api/cadastre/localisant?code_insee=94067§ion=invalid') 22 | .expect(400,done); 23 | }); 24 | }); 25 | 26 | describe('With invalid code_arr', function() { 27 | it('should reply with 400', function(done){ 28 | request(app) 29 | .get('/api/cadastre/localisant?code_insee=94067&code_arr=invalid') 30 | .expect(400,done); 31 | }); 32 | }); 33 | 34 | describe('With invalid com_abs', function() { 35 | it('should reply with 400', function(done){ 36 | request(app) 37 | .get('/api/cadastre/localisant?code_insee=94067&com_abs=invalid') 38 | .expect(400,done); 39 | }); 40 | }); 41 | 42 | }); 43 | 44 | 45 | it('/api/cadastre/localisant?code_insee=94067', function(done){ 46 | request(app) 47 | .get('/api/cadastre/localisant?code_insee=94067') 48 | .expect(200,done); 49 | }); 50 | 51 | it('/api/cadastre/localisant?code_insee=55001§ion=ZK&numero=0141', done => { 52 | request(app) 53 | .get('/api/cadastre/localisant?code_insee=55001§ion=ZK&numero=0141') 54 | .expect(res => { 55 | const feature = res.body.features[0]; 56 | expect(feature.geometry.type).to.eql('MultiPoint'); 57 | expect(feature.properties).to.eql({ 58 | gid: 14654203, 59 | numero: '0141', 60 | feuille: 1, 61 | section: 'ZK', 62 | code_dep: '55', 63 | nom_com: 'Abainville', 64 | code_com: '001', 65 | com_abs: '000', 66 | code_arr: '000', 67 | idu: "55001000ZK0141", 68 | code_insee: '55001' 69 | }); 70 | expect(feature.bbox).to.eql( 71 | [5.50374285,48.52731102,5.50374285,48.52731102] 72 | ) 73 | }) 74 | .end(done); 75 | }); 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /test/controllers/cadastre/parcelle.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import { app } from '../../../app.js'; 4 | 5 | describe('Testing /api/cadastre/parcelle', function() { 6 | describe('With invalid inputs', function() { 7 | 8 | describe('With invalid code_insee', function() { 9 | it('should reply with 400 for insee=testapi', function(done){ 10 | request(app) 11 | .get('/api/cadastre/parcelle?code_insee=testapi') 12 | .expect(400,done); 13 | }); 14 | }); 15 | 16 | describe('With invalid section', function() { 17 | it('should reply with 400', function(done){ 18 | request(app) 19 | .get('/api/cadastre/parcelle?code_insee=94067§ion=invalid') 20 | .expect(400,done); 21 | }); 22 | }); 23 | 24 | describe('With invalid code_arr', function() { 25 | it('should reply with 400', function(done){ 26 | request(app) 27 | .get('/api/cadastre/parcelle?code_insee=94067&code_arr=invalid') 28 | .expect(400,done); 29 | }); 30 | }); 31 | 32 | describe('With invalid com_abs', function() { 33 | it('should reply with 400', function(done){ 34 | request(app) 35 | .get('/api/cadastre/parcelle?code_insee=94067&com_abs=invalid') 36 | .expect(400,done); 37 | }); 38 | }); 39 | 40 | }); 41 | 42 | describe('/api/cadastre/parcelle?code_insee=94067§ion=0A&com_abs=410&numero=0112', function() { 43 | it('should work for insee 33290 et section=0A et numero=0112 et com_abs=410', function(done){ 44 | request(app) 45 | .get('/api/cadastre/parcelle?code_insee=94067§ion=0A&numero=0112&com_abs=410') 46 | .expect(200,done); 47 | }); 48 | }); 49 | 50 | }); 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /test/controllers/codes-postaux/communes.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import { app } from '../../../app.js'; 4 | 5 | describe('/codes-postaux', function() { 6 | describe('/communes', function() { 7 | describe('too short postal code', function() { 8 | it('should reply with 400', function(done) { 9 | request(app) 10 | .get('/api/codes-postaux/communes/7800') 11 | .expect(400, done); 12 | }); 13 | }); 14 | 15 | describe('too long postal code', function() { 16 | it('should reply with 400', function(done) { 17 | request(app) 18 | .get('/api/codes-postaux/communes/780000') 19 | .expect(400, done); 20 | }); 21 | }); 22 | 23 | describe('alphabetic input', function() { 24 | it('should reply with 400', function(done) { 25 | request(app) 26 | .get('/api/codes-postaux/communes/2A230') 27 | .expect(400, done); 28 | }); 29 | }); 30 | 31 | describe('non-existent postal code', function() { 32 | it('should reply with 404', function(done) { 33 | request(app) 34 | .get('/api/codes-postaux/communes/78123') 35 | .expect(404, done); 36 | }); 37 | }); 38 | 39 | describe('legit postal code', function() { 40 | const POSTAL_CODE = '06650'; 41 | const PAYLOAD = [ { 42 | codeCommune: '06089', 43 | nomCommune: 'Opio', 44 | codePostal: POSTAL_CODE, 45 | libelleAcheminement: 'OPIO' 46 | }, { 47 | codeCommune: '06112', 48 | nomCommune: 'Le Rouret', 49 | codePostal: POSTAL_CODE, 50 | libelleAcheminement: 'LE ROURET' 51 | } ]; 52 | 53 | it('should reply with 200', function(done) { 54 | request(app) 55 | .get(`/api/codes-postaux/communes/${POSTAL_CODE}`) 56 | .expect(200, done); 57 | }); 58 | 59 | it('should reply with a JSON payload', function(done) { 60 | request(app) 61 | .get(`/api/codes-postaux/communes/${POSTAL_CODE}`) 62 | .expect('Content-Type', /json/) 63 | .end(done); 64 | }); 65 | 66 | it('should reply with an array', function(done) { 67 | request(app) 68 | .get(`/api/codes-postaux/communes/${POSTAL_CODE}`) 69 | .expect(PAYLOAD, done); 70 | }); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/controllers/er/category.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | const EXPECTED_PROPERTIES = [ 7 | "back_image", 8 | "background_color", 9 | "border_color", 10 | "category_id", 11 | "code_article", 12 | "code_ean", 13 | "collection_slug", 14 | "collection_title", 15 | "complement", 16 | "continent", 17 | "deleted_at", 18 | "departement", 19 | "dimension", 20 | "ean_symb", 21 | "edition_number", 22 | "editorial", 23 | "er_visible_from", 24 | "er_visible_to", 25 | "front_image", 26 | "full_description", 27 | "has_geometry", 28 | "is_manufactured", 29 | "keywords", 30 | "name", 31 | "name_complement", 32 | "pays", 33 | "previous_publication_date", 34 | "price", 35 | "price_excluding_vat", 36 | "pricecode", 37 | "print_medium", 38 | "producer", 39 | "publication_date", 40 | "region", 41 | "replacement", 42 | "sale", 43 | "scale", 44 | "segment_slug", 45 | "segment_title", 46 | "theme_slug", 47 | "theme_title", 48 | "tva_type", 49 | "updated_at", 50 | "vat" 51 | ]; 52 | 53 | describe('Testing /api/er/category', function() { 54 | 55 | describe('With invalid inputs', function() { 56 | 57 | describe('With invalid type', function() { 58 | it('should reply with 400', function(done){ 59 | request(app) 60 | .get('/api/er/category?type=not_valid') 61 | .expect(400,done) 62 | ; 63 | }); 64 | }); 65 | 66 | describe('With missing type argument', function() { 67 | it('should reply with 400', function(done){ 68 | request(app) 69 | .get('/api/er/category?name=CARTES DE RANDONNÉE') 70 | .expect(400,done) 71 | ; 72 | }); 73 | }); 74 | }); 75 | 76 | describe('/api/er/category?name=CARTES DE RANDONNÉE&type=s', function() { 77 | it('should reply a FeatureCollection containing a valid Feature for name=CARTES DE RANDONNÉE & type=s', done => { 78 | request(app) 79 | .get('/api/er/category?name=CARTES DE RANDONNÉE&type=s') 80 | .expect(200) 81 | .expect(res => { 82 | const feature = res.body.features[0]; 83 | expect(feature.geometry.type).to.eql('MultiPolygon'); 84 | let propertyNames = Object.keys(feature.properties); 85 | propertyNames.sort(); 86 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 87 | expect(feature.properties.name).to.eql("0317OT - ILE D'OUESSANT"); 88 | }) 89 | .end(done); 90 | }); 91 | }); 92 | 93 | describe('/api/er/category?category_id=13', function() { 94 | it('should reply a FeatureCollection containing a valid Feature for category_id=13', done => { 95 | request(app) 96 | .get('/api/er/category?category_id=13') 97 | .expect(200) 98 | .expect(res => { 99 | const feature = res.body.features[1]; 100 | expect(feature.geometry.type).to.eql('MultiPolygon'); 101 | let propertyNames = Object.keys(feature.properties); 102 | propertyNames.sort(); 103 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 104 | 105 | expect(feature.properties.code_ean).to.eql(9782758553601); 106 | expect(feature.properties.code_article).to.eql('0416ET'); 107 | expect(feature.properties.name).to.eql('0416ET - PLOUGUERNEAU LES ABERS'); 108 | expect(feature.properties.name_complement).to.eql(null); 109 | expect(feature.properties.edition_number).to.eql('6'); 110 | expect(feature.properties.producer).to.eql('IGN'); 111 | expect(feature.properties.scale).to.eql('1:25 000'); 112 | expect(feature.properties.print_medium).to.eql('Papier standard'); 113 | expect(feature.properties.category_id).to.eql(13); 114 | expect(feature.properties.segment_title).to.eql('CARTES DE RANDONNÉE'); 115 | expect(feature.properties.theme_title).to.eql('TOP 25 ET SÉRIE BLEUE'); 116 | 117 | expect(feature.bbox).to.eql( 118 | [-4.82454566, 119 | 48.44183799, 120 | -4.35927545, 121 | 48.67786421]); 122 | }) 123 | .end(done); 124 | }); 125 | }); 126 | }); -------------------------------------------------------------------------------- /test/controllers/er/grid.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | const EXPECTED_PROPERTIES = [ 7 | "deleted", 8 | "name", 9 | "title", 10 | "type", 11 | "zip_codes" 12 | ]; 13 | 14 | describe('Testing /api/er/grid', function() { 15 | 16 | describe('With invalid inputs', function() { 17 | 18 | describe('With invalid geom', function() { 19 | it('should reply with 400', function(done){ 20 | request(app) 21 | .get('/api/er/grid?geom=not_valid') 22 | .expect(400,done) 23 | ; 24 | }); 25 | }); 26 | 27 | }); 28 | 29 | describe('/api/er/grid?name=01025', function() { 30 | it('should reply a FeatureCollection containing a valid Feature for name=01025', done => { 31 | request(app) 32 | .get('/api/er/grid?name=01025') 33 | .expect(200) 34 | .expect(res => { 35 | const feature = res.body.features[0]; 36 | expect(feature.geometry.type).to.eql('MultiPolygon'); 37 | let propertyNames = Object.keys(feature.properties); 38 | propertyNames.sort(); 39 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 40 | expect(feature.properties.title).to.eql("BAGE-DOMMARTIN"); 41 | }) 42 | .end(done); 43 | }); 44 | }); 45 | 46 | describe('/api/er/grid?title=BEAUPONT', function() { 47 | it('should reply a FeatureCollection containing a valid Feature for title=BEAUPONT', done => { 48 | request(app) 49 | .get('/api/er/grid?title=BEAUPONT') 50 | .expect(200) 51 | .expect(res => { 52 | const feature = res.body.features[0]; 53 | expect(feature.geometry.type).to.eql('MultiPolygon'); 54 | let propertyNames = Object.keys(feature.properties); 55 | propertyNames.sort(); 56 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 57 | 58 | expect(feature.properties.name).to.eql('01029'); 59 | expect(feature.properties.type).to.eql('municipality'); 60 | expect(feature.properties.zip_codes).to.eql('[\"01270\"]'); 61 | expect(feature.properties.title).to.eql('BEAUPONT'); 62 | expect(feature.properties.deleted).to.eql(false); 63 | 64 | expect(feature.bbox).to.eql( 65 | [5.23331158, 66 | 46.39351348, 67 | 5.29357027, 68 | 46.46135244]); 69 | }) 70 | .end(done); 71 | }); 72 | }); 73 | }); -------------------------------------------------------------------------------- /test/controllers/er/product.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | const EXPECTED_PROPERTIES = [ 7 | "back_image", 8 | "background_color", 9 | "border_color", 10 | "category_id", 11 | "code_article", 12 | "code_ean", 13 | "collection_slug", 14 | "collection_title", 15 | "complement", 16 | "continent", 17 | "deleted_at", 18 | "departement", 19 | "dimension", 20 | "ean_symb", 21 | "edition_number", 22 | "editorial", 23 | "er_visible_from", 24 | "er_visible_to", 25 | "front_image", 26 | "full_description", 27 | "has_geometry", 28 | "is_manufactured", 29 | "keywords", 30 | "name", 31 | "name_complement", 32 | "pays", 33 | "previous_publication_date", 34 | "price", 35 | "price_excluding_vat", 36 | "pricecode", 37 | "print_medium", 38 | "producer", 39 | "publication_date", 40 | "region", 41 | "replacement", 42 | "sale", 43 | "scale", 44 | "segment_slug", 45 | "segment_title", 46 | "theme_slug", 47 | "theme_title", 48 | "tva_type", 49 | "updated_at", 50 | "vat" 51 | ]; 52 | 53 | describe('Testing /api/er/product', function() { 54 | 55 | describe('With invalid inputs', function() { 56 | 57 | describe('With invalid code_ean', function() { 58 | it('should reply with 400', function(done){ 59 | request(app) 60 | .get('/api/er/product?code_ean=not_valid') 61 | .expect(400,done) 62 | ; 63 | }); 64 | }); 65 | 66 | }); 67 | 68 | describe('/api/er/product?code_ean=99782758554226', function() { 69 | it('should reply a FeatureCollection containing a valid Feature for code_ean=99782758554226', done => { 70 | request(app) 71 | .get('/api/er/product?code_ean=9782758554226') 72 | .expect(200) 73 | .expect(res => { 74 | const feature = res.body.features[0]; 75 | expect(feature.geometry.type).to.eql('MultiPolygon'); 76 | let propertyNames = Object.keys(feature.properties); 77 | propertyNames.sort(); 78 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 79 | expect(feature.properties.name).to.eql("0317OT - ILE D'OUESSANT"); 80 | }) 81 | .end(done); 82 | }); 83 | }); 84 | 85 | describe('/api/er/product?code_article=0416ET', function() { 86 | it('should reply a FeatureCollection containing a valid Feature for code_article=0416ET', done => { 87 | request(app) 88 | .get('/api/er/product?code_article=0416ET') 89 | .expect(200) 90 | .expect(res => { 91 | const feature = res.body.features[0]; 92 | expect(feature.geometry.type).to.eql('MultiPolygon'); 93 | let propertyNames = Object.keys(feature.properties); 94 | propertyNames.sort(); 95 | expect(propertyNames).to.eql(EXPECTED_PROPERTIES); 96 | 97 | expect(feature.properties.code_ean).to.eql(9782758553601); 98 | expect(feature.properties.code_article).to.eql('0416ET'); 99 | expect(feature.properties.name).to.eql('0416ET - PLOUGUERNEAU LES ABERS'); 100 | expect(feature.properties.name_complement).to.eql(null); 101 | expect(feature.properties.edition_number).to.eql('6'); 102 | expect(feature.properties.producer).to.eql('IGN'); 103 | expect(feature.properties.scale).to.eql('1:25 000'); 104 | expect(feature.properties.print_medium).to.eql('Papier standard'); 105 | expect(feature.properties.category_id).to.eql(13); 106 | expect(feature.properties.segment_title).to.eql('CARTES DE RANDONNÉE'); 107 | expect(feature.properties.theme_title).to.eql('TOP 25 ET SÉRIE BLEUE'); 108 | 109 | expect(feature.bbox).to.eql( 110 | [-4.82454566, 111 | 48.44183799, 112 | -4.35927545, 113 | 48.67786421]); 114 | }) 115 | .end(done); 116 | }); 117 | }); 118 | }); -------------------------------------------------------------------------------- /test/controllers/gpu/all.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | describe('/api/gpu/all', function() { 6 | 7 | describe('with invalid geom', function() { 8 | it('should reply an error', function(done) { 9 | request(app) 10 | .get('/api/gpu/all?geom={"type":"Point","coordinates":[1.654399]}') 11 | .expect(400) 12 | .end(done); 13 | ; 14 | }); 15 | }); 16 | 17 | 18 | describe('with point at [1.654399,48.112235] (Rennes)', function() { 19 | it('should reply a list of FeatureCollection', function(done) { 20 | // en attente de résolution problème de type sur assiette-sup-p 21 | request(app) 22 | .get('/api/gpu/all?geom={"type":"Point","coordinates":[1.654399,48.112235]}') 23 | .expect(200) 24 | .expect(res => { 25 | //TODO vérifier les specs 26 | expect(res.body).to.be.an('array'); 27 | expect(res.body.length).to.equal(17); 28 | res.body.forEach(function(featureCollection){ 29 | expect(featureCollection.type).to.equal('FeatureCollection'); 30 | }); 31 | }) 32 | .end(done); 33 | ; 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/controllers/gpu/document.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | describe('/api/gpu/document', function() { 6 | 7 | describe('with point at [1.654399,48.112235] (Rennes)', function() { 8 | it('should reply a FeatureCollection containing a valid Feature', function(done) { 9 | request(app) 10 | .get('/api/gpu/document?geom={"type":"Point","coordinates":[1.654399,48.112235]}') 11 | .expect(200) 12 | .expect(res => { 13 | expect(res.body.features.length).to.eql(1); 14 | const feature = res.body.features[0]; 15 | expect(feature.geometry.type).to.eql('MultiPolygon'); 16 | expect(feature.properties.du_type).to.eql('PLUi'); 17 | expect(feature.properties.partition).to.eql('DU_200070159'); 18 | }) 19 | .end(done); 20 | ; 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/controllers/gpu/municipality.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('with insee=25349', function() { 7 | it('should reply with a valid feature', function(done) { 8 | request(app) 9 | .get('/api/gpu/municipality?insee=25349') 10 | .expect(200) 11 | .expect(res => { 12 | expect(res.body.features.length).to.eql(1); 13 | const feature = res.body.features[0]; 14 | expect(feature.properties.name).to.eql('LORAY'); 15 | }) 16 | .end(done); 17 | ; 18 | }); 19 | }); 20 | 21 | describe('with point at [1.654399,48.112235] (Rennes)', function() { 22 | it('should reply a FeatureCollection containing a valid Feature', function(done) { 23 | request(app) 24 | .get('/api/gpu/municipality?geom={"type":"Point","coordinates":[1.654399,48.112235]}') 25 | .expect(200) 26 | .expect(res => { 27 | expect(res.body.features.length).to.eql(1); 28 | const feature = res.body.features[0]; 29 | expect(feature.properties.is_rnu).to.eql(false); 30 | }) 31 | .end(done); 32 | ; 33 | }); 34 | }); 35 | 36 | // describe('/api/gpu/municipality', function() { 37 | // describe('without filtering parameter', function() { 38 | // it('should reply with 400', function(done) { 39 | // request(app) 40 | // .get('/api/gpu/municipality') 41 | // .expect(200) 42 | // .expect(res => { 43 | // expect(res.body.features.length).to.be.greaterThan(10); 44 | // }) 45 | // .end(done); 46 | // }); 47 | // }); 48 | // }); 49 | -------------------------------------------------------------------------------- /test/controllers/gpu/zone-urba.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('/api/gpu/zone-urba', function() { 7 | 8 | describe('with point at [1.654399,48.112235] (Rennes)', function() { 9 | it('should reply a FeatureCollection containing a valid Feature', function(done) { 10 | request(app) 11 | .get('/api/gpu/zone-urba?geom={"type":"Point","coordinates":[1.654399,48.112235]}') 12 | .expect(200) 13 | .expect(res => { 14 | expect(res.body.features.length).to.eql(1); 15 | const feature = res.body.features[0]; 16 | expect(feature.geometry.type).to.eql('MultiPolygon'); 17 | expect(feature.properties.libelle).to.eql('A'); 18 | }) 19 | .end(done); 20 | ; 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/controllers/limites-administratives/commune.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('Testing /api/limites-administratives/commune', function() { 7 | 8 | describe('With invalid inputs', function() { 9 | 10 | describe('With invalid geom', function() { 11 | it('should reply with 400', function(done){ 12 | request(app) 13 | .get('/api/limites-administratives/commune?geom=not_valid') 14 | .expect(400,done) 15 | ; 16 | }); 17 | }); 18 | 19 | }); 20 | 21 | describe('/api/limites-administratives/commune?geom={"type":"Point","coordinates":[4.7962,45.22456]}',function(){ 22 | it('should reply a FeatureCollection with valid features', done => { 23 | request(app) 24 | .post('/api/limites-administratives/commune') 25 | .expect(200) 26 | .send({ 'geom': {"type":"Point","coordinates":[4.7962,45.22456]}}) 27 | .expect(res => { 28 | const feature = res.body.features[0]; 29 | expect(feature.geometry.type).to.eql('MultiPolygon'); 30 | expect(feature.properties).to.eql({ 31 | "id": "COMMUNE_0000000009754224", 32 | "nom_com": "Andance", 33 | "nom_com_m": "ANDANCE", 34 | "insee_com": "07009", 35 | "statut": "Commune simple", 36 | "population": "1197", 37 | "insee_arr": "3", 38 | "insee_dep": "07", 39 | "insee_reg": "84", 40 | "code_epci": "200040491", 41 | "nom_dep": "Ardèche", 42 | "nom_reg": "Auvergne-Rhône-Alpes" 43 | }); 44 | expect(feature.bbox).to.eql( 45 | [ 46 | 4.78195762, 47 | 45.20311472, 48 | 4.81250422, 49 | 45.26006374 50 | ] 51 | ); 52 | }) 53 | .end(done); 54 | }); 55 | }); 56 | 57 | describe('/api/limites-administratives/commune?lon=4.7962&lat=45.22456',function(){ 58 | it('should reply a FeatureCollection with valid features', done => { 59 | request(app) 60 | .get('/api/limites-administratives/commune?lon=4.7962&lat=45.22456') 61 | .expect(200) 62 | .expect(res => { 63 | const feature = res.body.features[0]; 64 | expect(feature.geometry.type).to.eql('MultiPolygon'); 65 | expect(feature.properties).to.eql({ 66 | "id": "COMMUNE_0000000009754224", 67 | "nom_com": "Andance", 68 | "nom_com_m": "ANDANCE", 69 | "insee_com": "07009", 70 | "statut": "Commune simple", 71 | "population": "1197", 72 | "insee_arr": "3", 73 | "insee_dep": "07", 74 | "insee_reg": "84", 75 | "code_epci": "200040491", 76 | "nom_dep": "Ardèche", 77 | "nom_reg": "Auvergne-Rhône-Alpes" 78 | }); 79 | expect(feature.bbox).to.eql( 80 | [ 81 | 4.78195762, 82 | 45.20311472, 83 | 4.81250422, 84 | 45.26006374 85 | ] 86 | ); 87 | }) 88 | .end(done); 89 | }); 90 | }); 91 | 92 | 93 | }); 94 | -------------------------------------------------------------------------------- /test/controllers/limites-administratives/departement.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('Testing /api/limites-administratives/departement', function() { 7 | 8 | describe('With invalid inputs', function() { 9 | 10 | describe('With invalid geom', function() { 11 | it('should reply with 400', function(done){ 12 | request(app) 13 | .get('/api/limites-administratives/departement?geom=not_valid') 14 | .expect(400,done) 15 | ; 16 | }); 17 | }); 18 | 19 | }); 20 | 21 | describe('/api/limites-administratives/departement?geom={"type":"Point","coordinates":[4.7962,45.22456]}',function(){ 22 | it('should reply a FeatureCollection with valid features', done => { 23 | request(app) 24 | .post('/api/limites-administratives/departement') 25 | .expect(200) 26 | .send({ 'geom': {"type":"Point","coordinates":[4.7962,45.22456]}}) 27 | .expect(res => { 28 | const feature = res.body.features[0]; 29 | expect(feature.geometry.type).to.eql('MultiPolygon'); 30 | expect(feature.properties).to.eql({ 31 | "id": "DEPARTEM_FXX_00000000009", 32 | "nom_dep": "Ardèche", 33 | "insee_dep": "07", 34 | "insee_reg": "84" 35 | }); 36 | expect(feature.bbox).to.eql( 37 | [ 38 | 3.86109916, 39 | 44.2643371, 40 | 4.88647239, 41 | 45.36619392 42 | ] 43 | ); 44 | }) 45 | .end(done); 46 | }); 47 | }); 48 | 49 | describe('/api/limites-administratives/departement?lon=4.7962&lat=45.22456',function(){ 50 | it('should reply a FeatureCollection with valid features', done => { 51 | request(app) 52 | .get('/api/limites-administratives/departement?lon=4.7962&lat=45.22456') 53 | .expect(200) 54 | .expect(res => { 55 | const feature = res.body.features[0]; 56 | expect(feature.geometry.type).to.eql('MultiPolygon'); 57 | expect(feature.properties).to.eql({ 58 | "id": "DEPARTEM_FXX_00000000009", 59 | "nom_dep": "Ardèche", 60 | "insee_dep": "07", 61 | "insee_reg": "84" 62 | }); 63 | expect(feature.bbox).to.eql( 64 | [ 65 | 3.86109916, 66 | 44.2643371, 67 | 4.88647239, 68 | 45.36619392 69 | ] 70 | ); 71 | }) 72 | .end(done); 73 | }); 74 | }); 75 | 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /test/controllers/limites-administratives/region.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import expect from 'expect.js'; 4 | import { app } from '../../../app.js'; 5 | 6 | describe('Testing /api/limites-administratives/region', function() { 7 | 8 | describe('With invalid inputs', function() { 9 | 10 | describe('With invalid geom', function() { 11 | it('should reply with 400', function(done){ 12 | request(app) 13 | .get('/api/limites-administratives/region?geom=not_valid') 14 | .expect(400,done) 15 | ; 16 | }); 17 | }); 18 | 19 | }); 20 | 21 | describe('/api/limites-administratives/region?geom={"type":"Point","coordinates":[4.7962,45.22456]}',function(){ 22 | it('should reply a FeatureCollection with valid features', done => { 23 | request(app) 24 | .post('/api/limites-administratives/region') 25 | .expect(200) 26 | .send({ 'geom': {"type":"Point","coordinates":[4.7962,45.22456]}}) 27 | .expect(res => { 28 | const feature = res.body.features[0]; 29 | expect(feature.geometry.type).to.eql('MultiPolygon'); 30 | expect(feature.properties).to.eql({ 31 | "id": "REGION_FXX_0000000000011", 32 | "nom_reg": "Auvergne-Rhône-Alpes", 33 | "insee_reg": "84" 34 | }); 35 | expect(feature.bbox).to.eql( 36 | [ 37 | 2.06287815, 38 | 44.11549261, 39 | 7.1855646, 40 | 46.80428705 41 | ] 42 | ); 43 | }) 44 | .end(done); 45 | }); 46 | }); 47 | 48 | describe('/api/limites-administratives/region?lon=4.7962&lat=45.22456',function(){ 49 | it('should reply a FeatureCollection with valid features', done => { 50 | request(app) 51 | .get('/api/limites-administratives/region?lon=4.7962&lat=45.22456') 52 | .expect(200) 53 | .expect(res => { 54 | const feature = res.body.features[0]; 55 | expect(feature.geometry.type).to.eql('MultiPolygon'); 56 | expect(feature.properties).to.eql({ 57 | "id": "REGION_FXX_0000000000011", 58 | "nom_reg": "Auvergne-Rhône-Alpes", 59 | "insee_reg": "84" 60 | }); 61 | expect(feature.bbox).to.eql( 62 | [ 63 | 2.06287815, 64 | 44.11549261, 65 | 7.1855646, 66 | 46.80428705 67 | ] 68 | ); 69 | }) 70 | .end(done); 71 | }); 72 | }); 73 | 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /test/controllers/rpg/rpgv1.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import { app } from '../../../app.js'; 4 | 5 | describe('/api/rpg/v1', function() { 6 | describe('without filtering parameter', function() { 7 | it('should reply with 400', function(done) { 8 | request(app) 9 | .get('/api/rpg/v1') 10 | .expect(400, done); 11 | }); 12 | }); 13 | 14 | describe('with invalid geom', function() { 15 | it('should reply an error', function(done) { 16 | request(app) 17 | .get('/api/rpg/v1?geom={"type":"Point","coordinates":[1.654399]}') 18 | .expect(400) 19 | .end(done); 20 | ; 21 | }); 22 | }); 23 | 24 | 25 | /*describe('with point at [1.654399,48.112235] (Rennes)', function() { 26 | it('should reply a list of FeatureCollection', function(done) { 27 | // en attente de résolution problème de type sur assiette-sup-p 28 | request(app) 29 | .get('/api/rpg/v1?annee=2013&geom={"type":"Point","coordinates":[1.654399,48.112235]}') 30 | .expect(200) 31 | .expect(res => { 32 | //TODO vérifier les specs 33 | expect(res.body).to.be.an('array'); 34 | expect(res.body.length).to.eql(1); 35 | res.body.forEach(function(featureCollection){ 36 | expect(featureCollection.type).to.equal('FeatureCollection'); 37 | }); 38 | }) 39 | .end(done); 40 | ; 41 | }); 42 | });*/ 43 | }); 44 | -------------------------------------------------------------------------------- /test/controllers/rpg/rpgv2.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | import request from 'supertest'; 3 | import { app } from '../../../app.js'; 4 | 5 | describe('/api/rpg/v2', function() { 6 | describe('without filtering parameter', function() { 7 | it('should reply with 400', function(done) { 8 | request(app) 9 | .get('/api/rpg/v2') 10 | .expect(400, done); 11 | }); 12 | }); 13 | 14 | describe('with invalid geom', function() { 15 | it('should reply an error', function(done) { 16 | request(app) 17 | .get('/api/rpg/v2?annee=2018&geom={"type":"Point","coordinates":[1.654399]}') 18 | .expect(400) 19 | .end(done); 20 | ; 21 | }); 22 | }); 23 | 24 | 25 | /*describe('with point at [1.654399,48.112235] (Rennes)', function() { 26 | it('should reply a list of FeatureCollection', function(done) { 27 | request(app) 28 | .get('/api/rpg/v2?annee=2018&geom={"type":"Point","coordinates":[1.654399,48.112235]}') 29 | .expect(200) 30 | .expect(res => { 31 | //TODO vérifier les specs 32 | expect(res.body).to.be.an('array'); 33 | expect(res.body.length).to.eql(1); 34 | res.body.forEach(function(featureCollection){ 35 | expect(featureCollection.type).to.equal("FeatureCollection"); 36 | }); 37 | }) 38 | .end(done); 39 | ; 40 | }); 41 | });*/ 42 | }); -------------------------------------------------------------------------------- /test/helper/parseInseeCode.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | import expect from 'expect.js'; 3 | import parseInseeCode from '../../helper/parseInseeCode.js'; 4 | 5 | describe('#parseCodeInsee()', () => { 6 | describe('with invalid INSEE codes', () => { 7 | [ 8 | '5444', // too short 9 | '544477', // too long 10 | '2E001', 11 | '24A01', 12 | '00000' 13 | ].forEach(invalidCode => { 14 | it(`should reject ${invalidCode}`, () => { 15 | expect(parseInseeCode) 16 | .withArgs(invalidCode) 17 | .to.throwException('Invalid INSEE code'); 18 | }); 19 | }); 20 | }); 21 | 22 | it('should parse basic INSEE code', () => { 23 | expect(parseInseeCode('25349')).to.eql({ 24 | code_dep: '25', 25 | code_com: '349' 26 | }); 27 | }); 28 | 29 | it('should parse an INSEE code in 93', () => { 30 | expect(parseInseeCode('93233')).to.eql({ 31 | code_dep: '93', 32 | code_com: '233' 33 | }); 34 | }); 35 | 36 | it('should parse DOM/TOM INSEE code', () => { 37 | expect(parseInseeCode('97209')).to.eql({ 38 | code_dep: '97', 39 | code_com: '209' 40 | }); 41 | }); 42 | 43 | it('should parse Corse INSEE code', () => { 44 | expect(parseInseeCode('2A001')).to.eql({ 45 | code_dep: '2A', 46 | code_com: '001' 47 | }); 48 | }); 49 | }); 50 | --------------------------------------------------------------------------------