@ignfab/geocontext 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +34 -11
  2. package/dist/gpf/adminexpress.d.ts +2 -1
  3. package/dist/gpf/adminexpress.js +7 -3
  4. package/dist/gpf/adminexpress.js.map +1 -1
  5. package/dist/gpf/altitude.d.ts +2 -1
  6. package/dist/gpf/altitude.js +13 -19
  7. package/dist/gpf/altitude.js.map +1 -1
  8. package/dist/gpf/geocode.d.ts +3 -1
  9. package/dist/gpf/geocode.js +19 -8
  10. package/dist/gpf/geocode.js.map +1 -1
  11. package/dist/gpf/parcellaire-express.d.ts +2 -1
  12. package/dist/gpf/parcellaire-express.js +8 -4
  13. package/dist/gpf/parcellaire-express.js.map +1 -1
  14. package/dist/gpf/urbanisme.d.ts +4 -2
  15. package/dist/gpf/urbanisme.js +32 -5
  16. package/dist/gpf/urbanisme.js.map +1 -1
  17. package/dist/gpf/wfs.d.ts +5 -3
  18. package/dist/gpf/wfs.js +59 -19
  19. package/dist/gpf/wfs.js.map +1 -1
  20. package/dist/logger.js.map +1 -1
  21. package/dist/resources/WfsCqlFilterResource.d.ts +10 -0
  22. package/dist/resources/WfsCqlFilterResource.js +21 -0
  23. package/dist/resources/WfsCqlFilterResource.js.map +1 -0
  24. package/dist/resources/readMarkdownResource.d.ts +1 -0
  25. package/dist/resources/readMarkdownResource.js +21 -0
  26. package/dist/resources/readMarkdownResource.js.map +1 -0
  27. package/dist/tools/AdminexpressTool.d.ts +58 -15
  28. package/dist/tools/AdminexpressTool.js +33 -13
  29. package/dist/tools/AdminexpressTool.js.map +1 -1
  30. package/dist/tools/AltitudeTool.d.ts +64 -16
  31. package/dist/tools/AltitudeTool.js +30 -12
  32. package/dist/tools/AltitudeTool.js.map +1 -1
  33. package/dist/tools/AssietteSupTool.d.ts +64 -16
  34. package/dist/tools/AssietteSupTool.js +34 -13
  35. package/dist/tools/AssietteSupTool.js.map +1 -1
  36. package/dist/tools/CadastreTool.d.ts +68 -15
  37. package/dist/tools/CadastreTool.js +35 -13
  38. package/dist/tools/CadastreTool.js.map +1 -1
  39. package/dist/tools/GeocodeTool.d.ts +73 -10
  40. package/dist/tools/GeocodeTool.js +35 -9
  41. package/dist/tools/GeocodeTool.js.map +1 -1
  42. package/dist/tools/GpfWfsDescribeTypeTool.d.ts +115 -12
  43. package/dist/tools/GpfWfsDescribeTypeTool.js +39 -13
  44. package/dist/tools/GpfWfsDescribeTypeTool.js.map +1 -1
  45. package/dist/tools/GpfWfsGetFeaturesTool.d.ts +65 -28
  46. package/dist/tools/GpfWfsGetFeaturesTool.js +117 -47
  47. package/dist/tools/GpfWfsGetFeaturesTool.js.map +1 -1
  48. package/dist/tools/GpfWfsListTypesTool.d.ts +18 -6
  49. package/dist/tools/GpfWfsListTypesTool.js +16 -8
  50. package/dist/tools/GpfWfsListTypesTool.js.map +1 -1
  51. package/dist/tools/GpfWfsSearchTypesTool.d.ts +61 -14
  52. package/dist/tools/GpfWfsSearchTypesTool.js +38 -17
  53. package/dist/tools/GpfWfsSearchTypesTool.js.map +1 -1
  54. package/dist/tools/UrbanismeTool.d.ts +63 -15
  55. package/dist/tools/UrbanismeTool.js +42 -13
  56. package/dist/tools/UrbanismeTool.js.map +1 -1
  57. package/dist/tools/toolAnnotations.d.ts +6 -0
  58. package/dist/tools/toolAnnotations.js +7 -0
  59. package/dist/tools/toolAnnotations.js.map +1 -0
  60. package/package.json +3 -3
package/README.md CHANGED
@@ -14,7 +14,7 @@ L'idée est ici d'**expérimenter la conception d'un MCP rendant les données et
14
14
 
15
15
  ## Mises en garde
16
16
 
17
- - Ce développement est un POC en incubation avec IgnFab (archivage en cours de [mborne/geocontext](https://github.com/mborne/geocontext))
17
+ - Ce développement est un POC en incubation au sein d'ignfab (archivage en cours de [mborne/geocontext](https://github.com/mborne/geocontext))
18
18
  - S'il s'avère utile de l'industrialiser, le dépôt sera migré sous responsabilité IGN et l'outil sera renommé (ex : `IGNF/mcp-gpf-server`)
19
19
  - Plusieurs problèmes et améliorations possibles ont été identifiés et sont en cours de mitigation/résolution (c.f. [issues](https://github.com/ignfab/geocontext/issues?q=is%3Aissue%20state%3Aopen%20label%3Ametadata)).
20
20
  - Cet outil n'est pas magique (voir [Fonctionnalités](#fonctionnalités) pour avoir une idée de ses capacités)
@@ -74,6 +74,8 @@ npm run build
74
74
 
75
75
  ### Utilisation de la version locale
76
76
 
77
+ #### Avec un client MCP compatible JSON
78
+
77
79
  ```json
78
80
  {
79
81
  "mcpServers": {
@@ -85,6 +87,27 @@ npm run build
85
87
  }
86
88
  ```
87
89
 
90
+ #### Avec Codex CLI / VS Code
91
+
92
+ Avec Codex CLI ou l'extension Codex pour VS Code, la configuration se fait dans `~/.codex/config.toml` :
93
+
94
+ ```toml
95
+ [mcp_servers.geocontext]
96
+ command = "node"
97
+ args = ["/chemin/absolu/vers/geocontext/dist/index.js"]
98
+
99
+ # Définition d'un corporate proxy si nécessaire
100
+ [mcp_servers.geocontext.env]
101
+ HTTPS_PROXY = "http://proxy.domain.fr:3128"
102
+ HTTP_PROXY = "http://proxy.domain.fr:3128"
103
+ NO_PROXY = "localhost,127.0.0.1"
104
+ http_proxy = "http://proxy.domain.fr:3128"
105
+ https_proxy = "http://proxy.domain.fr:3128"
106
+ no_proxy = "localhost,127.0.0.1"
107
+ ```
108
+
109
+ Après mise à jour de `~/.codex/config.toml`, relancer la session Codex CLI ou recharger VS Code si l'extension Codex est déjà ouverte.
110
+
88
111
  ### Debug de la version locale
89
112
 
90
113
  ```bash
@@ -98,15 +121,15 @@ Pour une utilisation avancée :
98
121
  | Nom | Description | Valeur par défaut |
99
122
  | ---------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------- |
100
123
  | `TRANSPORT_TYPE` | [Transport](https://mcp-framework.com/docs/Transports/transports-overview) permet de choisir entre "stdio" et "http" | "stdio" |
101
- | `GPF_WFS_SEARCH_OPTIONS` | Chaîne JSON optionnelle pour ajuster la recherche `gpf_wfs_search_types` (`fuzzy`, `boost.namespace`, `boost.name`, `boost.title`, `boost.description`, `boost.properties`). | options par défaut de `@ignfab/gpf-schema-store` |
124
+ | `GPF_WFS_MINISEARCH_OPTIONS` | Chaîne JSON optionnelle pour ajuster les options MiniSearch utilisées par `gpf_wfs_search_types` (`fields`, `combineWith`, `fuzzy`, `boost.namespace`, `boost.name`, `boost.title`, `boost.description`, `boost.properties`, `boost.enums`, `boost.identifierTokens`). | options par défaut de `@ignfab/gpf-schema-store` |
102
125
 
103
126
  Exemple :
104
127
 
105
128
  ```bash
106
- export GPF_WFS_SEARCH_OPTIONS='{"fuzzy":0.05,"boost":{"title":4,"name":5}}'
129
+ export GPF_WFS_MINISEARCH_OPTIONS='{"fields":["title","identifierTokens"],"combineWith":"OR","fuzzy":0.05,"boost":{"title":4,"name":5}}'
107
130
  ```
108
131
 
109
- Si `GPF_WFS_SEARCH_OPTIONS` est absent ou vide, les options par défaut restent celles de `@ignfab/gpf-schema-store`.
132
+ Si `GPF_WFS_MINISEARCH_OPTIONS` est absent ou vide, les options par défaut restent celles de `@ignfab/gpf-schema-store`, y compris le comportement par défaut `OR` de MiniSearch pour `combineWith`.
110
133
 
111
134
  Remarque :
112
135
 
@@ -148,18 +171,18 @@ L'idée est ici de répondre à des précises en traitant côté serveur les app
148
171
 
149
172
  ### Explorer les données vecteurs
150
173
 
151
- #### Explorer les tables
152
-
153
- * [gpf_wfs_list_types()](src/tools/GpfWfsListTypesTool.ts) pour **lister les tables connues du catalogue de schémas embarqué** - **déprécié (trop de résultats)**
154
- * [gpf_wfs_search_types(keywords,max_results=10)](src/tools/GpfWfsSearchTypesTool.ts) pour **rechercher des tables dans le catalogue de schémas embarqué**. La recherche est textuelle et configurable via `GPF_WFS_SEARCH_OPTIONS`.
174
+ #### Explorer les tables
175
+
176
+ * [gpf_wfs_list_types()](src/tools/GpfWfsListTypesTool.ts) pour **lister de façon exhaustive les types WFS connus du catalogue de schémas embarqué**. Cet outil est surtout utile pour un inventaire complet ou une exploration globale du catalogue ; pour trouver rapidement un type pertinent, préférer `gpf_wfs_search_types`.
177
+ * [gpf_wfs_search_types(keywords,max_results=10)](src/tools/GpfWfsSearchTypesTool.ts) pour **rechercher un type WFS pertinent à partir de mots-clés et obtenir un `typename` valide**. La recherche est textuelle et configurable via `GPF_WFS_MINISEARCH_OPTIONS`.
155
178
 
156
179
  > - Quels sont les millésimes ADMINEXPRESS disponibles sur la Géoplateforme?
157
180
  > - Quelle est la table de la BDTOPO correspondant aux bâtiments?
158
181
  > - Dans quelle table de la BDTOPO peut-on trouver les ponts?
159
182
 
160
- #### Explorer la structure des tables
161
-
162
- * [gpf_wfs_describe_type(typename)](src/tools/GpfWfsDescribeTypeTool.ts) pour récupérer le **schéma détaillé d'une table** depuis le catalogue embarqué (`id`, `namespace`, `name`, `title`, `description`, `properties`)
183
+ #### Explorer la structure des tables
184
+
185
+ * [gpf_wfs_describe_type(typename)](src/tools/GpfWfsDescribeTypeTool.ts) pour récupérer le **schéma détaillé d'un type WFS** depuis le catalogue embarqué (`id`, `namespace`, `name`, `title`, `description`, `properties`), en particulier avant d'appeler `gpf_wfs_get_features`
163
186
 
164
187
  > - Quelles sont les informations disponibles pour les communes avec ADMINEXPRESS-COG.LATEST?
165
188
  > - Compare le modèle des communes entre ADMINEXPRESS-COG:2024 et ADMINEXPRESS-COG.LATEST
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * @param {number} lon
5
5
  * @param {number} lat
6
+ * @param {(url: string) => Promise<any>} [fetcher]
6
7
  * @returns {object[]}
7
8
  */
8
- export function getAdminUnits(lon: number, lat: number): object[];
9
+ export function getAdminUnits(lon: number, lat: number, fetcher?: (url: string) => Promise<any>): object[];
9
10
  /**
10
11
  * ADMINEXPRESS-COG.LATEST:{type}
11
12
  *
@@ -1,6 +1,6 @@
1
+ import _ from 'lodash';
1
2
  import { fetchJSON } from '../helpers/http.js';
2
3
  import logger from '../logger.js';
3
- import _ from 'lodash';
4
4
  /**
5
5
  * ADMINEXPRESS-COG.LATEST:{type}
6
6
  *
@@ -21,9 +21,10 @@ export const ADMINEXPRESS_TYPES = [
21
21
  *
22
22
  * @param {number} lon
23
23
  * @param {number} lat
24
+ * @param {(url: string) => Promise<any>} [fetcher]
24
25
  * @returns {object[]}
25
26
  */
26
- export async function getAdminUnits(lon, lat) {
27
+ export async function getAdminUnits(lon, lat, fetcher = fetchJSON) {
27
28
  logger.info(`[adminexpress] getAdminUnits(${lon},${lat})...`);
28
29
  // note that EPSG:4326 means lat,lon order for GeoServer -> flipped coordinates...
29
30
  const cql_filter = `INTERSECTS(geometrie,Point(${lat} ${lon}))`;
@@ -35,7 +36,10 @@ export async function getAdminUnits(lon, lat) {
35
36
  outputFormat: 'application/json',
36
37
  cql_filter: cql_filter
37
38
  }).toString();
38
- const featureCollection = await fetchJSON(url);
39
+ const featureCollection = await fetcher(url);
40
+ if (!Array.isArray(featureCollection?.features)) {
41
+ throw new Error("Le service ADMINEXPRESS n'a pas retourné de collection d'objets exploitable");
42
+ }
39
43
  return featureCollection.features.map((feature) => {
40
44
  // parse type from id (ex: "commune.3837")
41
45
  const type = feature.id.split('.')[0];
@@ -1 +1 @@
1
- {"version":3,"file":"adminexpress.js","sourceRoot":"","sources":["../../src/gpf/adminexpress.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC,OAAO,CAAC,MAAM,QAAQ,CAAC;AAEvB;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,8CAA8C,CAAC;AAClF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAC9B,SAAS;IACT,QAAQ;IACR,2BAA2B;IAC3B,MAAM;IACN,aAAa;IACb,QAAQ;IACR,gBAAgB;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAG,EAAE,GAAG;IACxC,MAAM,CAAC,IAAI,CAAC,gCAAgC,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC;IAE9D,kFAAkF;IAClF,MAAM,UAAU,GAAG,8BAA8B,GAAG,IAAI,GAAG,IAAI,CAAC;IAEhE,uDAAuD;IACvD,MAAM,GAAG,GAAG,4BAA4B,GAAG,IAAI,eAAe,CAAC;QAC3D,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,YAAY;QACrB,QAAQ,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,2BAA2B,IAAI,EAAE,CAAA,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAClG,YAAY,EAAE,kBAAkB;QAChC,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEd,MAAM,iBAAiB,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9C,0CAA0C;QAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,wCAAwC;QACxC,OAAO,MAAM,CAAC,MAAM,CAAC;YACjB,IAAI,EAAE,IAAI;YACV,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;SACrB,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"adminexpress.js","sourceRoot":"","sources":["../../src/gpf/adminexpress.js"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,QAAQ,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,8CAA8C,CAAC;AAClF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAC9B,SAAS;IACT,QAAQ;IACR,2BAA2B;IAC3B,MAAM;IACN,aAAa;IACb,QAAQ;IACR,gBAAgB;CACnB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,SAAS;IAC7D,MAAM,CAAC,IAAI,CAAC,gCAAgC,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC;IAE9D,kFAAkF;IAClF,MAAM,UAAU,GAAG,8BAA8B,GAAG,IAAI,GAAG,IAAI,CAAC;IAEhE,uDAAuD;IACvD,MAAM,GAAG,GAAG,4BAA4B,GAAG,IAAI,eAAe,CAAC;QAC3D,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,YAAY;QACrB,QAAQ,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,2BAA2B,IAAI,EAAE,CAAA,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAClG,YAAY,EAAE,kBAAkB;QAChC,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEd,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9C,0CAA0C;QAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,wCAAwC;QACxC,OAAO,MAAM,CAAC,MAAM,CAAC;YACjB,IAAI,EAAE,IAAI;YACV,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;SACrB,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -5,9 +5,10 @@
5
5
  *
6
6
  * @param {number} lon
7
7
  * @param {number} lat
8
+ * @param {(url: string) => Promise<any>} [fetcher]
8
9
  * @returns
9
10
  */
10
- export function getAltitudeByLocation(lon: number, lat: number): Promise<{
11
+ export function getAltitudeByLocation(lon: number, lat: number, fetcher?: (url: string) => Promise<any>): Promise<{
11
12
  lon: number;
12
13
  lat: number;
13
14
  altitude: any;
@@ -1,5 +1,5 @@
1
- import logger from "../logger.js";
2
1
  import { fetchJSON } from "../helpers/http.js";
2
+ import logger from "../logger.js";
3
3
  export const ALTITUDE_SOURCE = "Géoplateforme (altimétrie)";
4
4
  /**
5
5
  * Get altitude for a given location.
@@ -8,28 +8,22 @@ export const ALTITUDE_SOURCE = "Géoplateforme (altimétrie)";
8
8
  *
9
9
  * @param {number} lon
10
10
  * @param {number} lat
11
+ * @param {(url: string) => Promise<any>} [fetcher]
11
12
  * @returns
12
13
  */
13
- export async function getAltitudeByLocation(lon, lat) {
14
+ export async function getAltitudeByLocation(lon, lat, fetcher = fetchJSON) {
14
15
  logger.info(`getAltitudeByLocation(${lon},${lat})...`);
15
16
  const url = `https://data.geopf.fr/altimetrie/1.0/calcul/alti/rest/elevation.json?lon=${lon}&lat=${lat}&resource=ign_rge_alti_wld`;
16
- try {
17
- const json = await fetchJSON(url);
18
- const elevation = json.elevations[0];
19
- return {
20
- lon: lon,
21
- lat: lat,
22
- altitude: elevation.z,
23
- accuracy: elevation.acc,
24
- };
25
- }
26
- catch (e) {
27
- return {
28
- lon: lon,
29
- lat: lat,
30
- altitude: null,
31
- accuracy: 'No data',
32
- };
17
+ const json = await fetcher(url);
18
+ const elevation = json?.elevations?.[0];
19
+ if (!elevation) {
20
+ throw new Error("Le service d'altitude n'a renvoyé aucune donnée d'altitude");
33
21
  }
22
+ return {
23
+ lon: lon,
24
+ lat: lat,
25
+ altitude: elevation.z,
26
+ accuracy: elevation.acc,
27
+ };
34
28
  }
35
29
  //# sourceMappingURL=altitude.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"altitude.js","sourceRoot":"","sources":["../../src/gpf/altitude.js"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAG,EAAE,GAAG;IAChD,MAAM,CAAC,IAAI,CAAC,yBAAyB,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC;IAEvD,MAAM,GAAG,GAAG,4EAA4E,GAAG,QAAQ,GAAG,4BAA4B,CAAC;IACnI,IAAI,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAE;QACtC,OAAO;YACH,GAAG,EAAE,GAAG;YACR,GAAG,EAAE,GAAG;YACR,QAAQ,EAAE,SAAS,CAAC,CAAC;YACrB,QAAQ,EAAE,SAAS,CAAC,GAAG;SAC1B,CAAC;IACN,CAAC;IAAA,OAAM,CAAC,EAAC,CAAC;QACN,OAAO;YACH,GAAG,EAAE,GAAG;YACR,GAAG,EAAE,GAAG;YACR,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,SAAS;SACtB,CAAC;IACN,CAAC;AAGL,CAAC"}
1
+ {"version":3,"file":"altitude.js","sourceRoot":"","sources":["../../src/gpf/altitude.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,SAAS;IACrE,MAAM,CAAC,IAAI,CAAC,yBAAyB,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC;IAEvD,MAAM,GAAG,GAAG,4EAA4E,GAAG,QAAQ,GAAG,4BAA4B,CAAC;IAEnI,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAExC,IAAI,CAAC,SAAS,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAClF,CAAC;IAED,OAAO;QACH,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,GAAG;QACR,QAAQ,EAAE,SAAS,CAAC,CAAC;QACrB,QAAQ,EAAE,SAAS,CAAC,GAAG;KAC1B,CAAC;AAEN,CAAC"}
@@ -4,7 +4,9 @@
4
4
  * @see https://geoservices.ign.fr/documentation/services/services-geoplateforme/autocompletion
5
5
  *
6
6
  * @param {string} text
7
+ * @param {number} [maximumResponses=3]
8
+ * @param {(url: string) => Promise<any>} [fetcher]
7
9
  * @returns
8
10
  */
9
- export function geocode(text: string): Promise<any>;
11
+ export function geocode(text: string, maximumResponses?: number, fetcher?: (url: string) => Promise<any>): Promise<any>;
10
12
  export const GEOCODE_SOURCE: "G\u00E9oplateforme (service d'autocompl\u00E9tion)";
@@ -1,26 +1,37 @@
1
- import logger from "../logger.js";
2
1
  import { fetchJSON } from "../helpers/http.js";
2
+ import logger from "../logger.js";
3
3
  export const GEOCODE_SOURCE = "Géoplateforme (service d'autocomplétion)";
4
+ // https://data.geopf.fr/geocodage/completion/openapi does not provide all the necessary information yet
4
5
  /**
5
6
  * Get coordinates for a given location
6
7
  *
7
8
  * @see https://geoservices.ign.fr/documentation/services/services-geoplateforme/autocompletion
8
9
  *
9
10
  * @param {string} text
11
+ * @param {number} [maximumResponses=3]
12
+ * @param {(url: string) => Promise<any>} [fetcher]
10
13
  * @returns
11
14
  */
12
- export async function geocode(text) {
13
- logger.info(`geocode(${JSON.stringify(text)})...`);
15
+ export async function geocode(text, maximumResponses = 3, fetcher = fetchJSON) {
16
+ const normalizedText = typeof text === "string" ? text.trim() : "";
17
+ if (!normalizedText) {
18
+ return [];
19
+ }
20
+ logger.info(`geocode(${JSON.stringify(normalizedText)}, ${maximumResponses})...`);
14
21
  const url = 'https://data.geopf.fr/geocodage/completion/?' + new URLSearchParams({
15
- text: text,
16
- maximumResponses: 3
22
+ text: normalizedText,
23
+ maximumResponses: String(maximumResponses)
17
24
  }).toString();
18
- const json = await fetchJSON(url);
19
- return json.results.map((item) => {
25
+ const json = await fetcher(url);
26
+ const results = Array.isArray(json?.results) ? json.results : [];
27
+ return results.map((item) => {
20
28
  return {
21
29
  lon: item.x,
22
30
  lat: item.y,
23
- fulltext: item.fulltext
31
+ fulltext: item.fulltext,
32
+ kind: item.kind,
33
+ city: item.city,
34
+ zipcode: item.zipcode
24
35
  };
25
36
  });
26
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"geocode.js","sourceRoot":"","sources":["../../src/gpf/geocode.js"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,CAAC,MAAM,cAAc,GAAG,0CAA0C,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAI;IAC9B,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEnD,MAAM,GAAG,GAAG,8CAA8C,GAAG,IAAI,eAAe,CAAC;QAC/E,IAAI,EAAE,IAAI;QACV,gBAAgB,EAAE,CAAC;KACpB,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEd,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAC,EAAE;QAAC,OAAO;YACtC,GAAG,EAAE,IAAI,CAAC,CAAC;YACX,GAAG,EAAE,IAAI,CAAC,CAAC;YACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAA;IAAA,CAAC,CAAC,CAAC;AACR,CAAC"}
1
+ {"version":3,"file":"geocode.js","sourceRoot":"","sources":["../../src/gpf/geocode.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC,MAAM,CAAC,MAAM,cAAc,GAAG,0CAA0C,CAAC;AAEzE,wGAAwG;AAExG;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS;IACzE,MAAM,cAAc,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnE,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,KAAK,gBAAgB,MAAM,CAAC,CAAC;IAElF,MAAM,GAAG,GAAG,8CAA8C,GAAG,IAAI,eAAe,CAAC;QAC/E,IAAI,EAAE,cAAc;QACpB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,CAAC;KAC3C,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEd,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAC,EAAE;QAAC,OAAO;YACjC,GAAG,EAAE,IAAI,CAAC,CAAC;YACX,GAAG,EAAE,IAAI,CAAC,CAAC;YACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAA;IAAA,CAAC,CAAC,CAAC;AACR,CAAC"}
@@ -5,8 +5,9 @@
5
5
  *
6
6
  * @param {number} lon
7
7
  * @param {number} lat
8
+ * @param {(url: string) => Promise<any>} [fetcher]
8
9
  * @returns
9
10
  */
10
- export function getParcellaireExpress(lon: number, lat: number): Promise<array<object>>;
11
+ export function getParcellaireExpress(lon: number, lat: number, fetcher?: (url: string) => Promise<any>): Promise<array<object>>;
11
12
  export const PARCELLAIRE_EXPRESS_SOURCE: "G\u00E9oplateforme (WFS, CADASTRALPARCELS.PARCELLAIRE_EXPRESS)";
12
13
  export const PARCELLAIRE_EXPRESS_TYPES: string[];
@@ -1,7 +1,7 @@
1
- import logger from '../logger.js';
2
- import distance from '../helpers/distance.js';
3
1
  import _ from 'lodash';
2
+ import distance from '../helpers/distance.js';
4
3
  import { fetchJSON } from '../helpers/http.js';
4
+ import logger from '../logger.js';
5
5
  // CADASTRALPARCELS.PARCELLAIRE_EXPRESS:
6
6
  // https://data.geopf.fr/wfs/ows?service=WFS&version=2.0.0&request=GetCapabilities
7
7
  export const PARCELLAIRE_EXPRESS_SOURCE = "Géoplateforme (WFS, CADASTRALPARCELS.PARCELLAIRE_EXPRESS)";
@@ -39,9 +39,10 @@ function filterByDistance(items) {
39
39
  *
40
40
  * @param {number} lon
41
41
  * @param {number} lat
42
+ * @param {(url: string) => Promise<any>} [fetcher]
42
43
  * @returns
43
44
  */
44
- export async function getParcellaireExpress(lon, lat) {
45
+ export async function getParcellaireExpress(lon, lat, fetcher = fetchJSON) {
45
46
  logger.info(`getParcellaireExpress(${lon},${lat}) ...`);
46
47
  // note that EPSG:4326 means lat,lon order for GeoServer -> flipped coordinates...
47
48
  const cql_filter = `DWITHIN(geom,Point(${lat} ${lon}),10,meters)`;
@@ -57,7 +58,10 @@ export async function getParcellaireExpress(lon, lat) {
57
58
  outputFormat: 'application/json',
58
59
  cql_filter: cql_filter
59
60
  }).toString();
60
- const featureCollection = await fetchJSON(url);
61
+ const featureCollection = await fetcher(url);
62
+ if (!Array.isArray(featureCollection?.features)) {
63
+ throw new Error("Le service PARCELLAIRE_EXPRESS n'a pas retourné de collection d'objets exploitable");
64
+ }
61
65
  return filterByDistance(featureCollection.features.map((feature) => {
62
66
  // parse type from id (ex: "commune.3837")
63
67
  const type = feature.id.split('.')[0];
@@ -1 +1 @@
1
- {"version":3,"file":"parcellaire-express.js","sourceRoot":"","sources":["../../src/gpf/parcellaire-express.js"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAE9C,OAAO,CAAC,MAAM,QAAQ,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,wCAAwC;AACxC,kFAAkF;AAElF,MAAM,CAAC,MAAM,0BAA0B,GAAG,2DAA2D,CAAC;AACtG,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACrC,gBAAgB;IAChB,SAAS;IACT,SAAS;IACT,UAAU;IACV,qBAAqB;IACrB,YAAY;CACf,CAAC;AAEF;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAK;IAC3B,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAE3E,MAAM,MAAM,GAAG,EAAE,CAAC;IAClB,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,KAAM,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC9B,IAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACzB,SAAS;QACb,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAGD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAG,EAAE,GAAG;IAChD,MAAM,CAAC,IAAI,CAAC,yBAAyB,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC;IACxD,kFAAkF;IAClF,MAAM,UAAU,GAAG,sBAAsB,GAAG,IAAI,GAAG,cAAc,CAAC;IAElE,MAAM,UAAU,GAAG;QACf,MAAM,EAAE,OAAO;QACf,aAAa,EAAE,CAAC,GAAG,EAAC,GAAG,CAAC;KAC3B,CAAC;IAEF,uDAAuD;IACvD,MAAM,GAAG,GAAG,4BAA4B,GAAG,IAAI,eAAe,CAAC;QAC3D,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,YAAY;QACrB,QAAQ,EAAE,yBAAyB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,wCAAwC,IAAI,EAAE,CAAA,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACtH,YAAY,EAAE,kBAAkB;QAChC,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEd,MAAM,iBAAiB,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO,gBAAgB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC/D,0CAA0C;QAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,wCAAwC;QACxC,OAAO,MAAM,CAAC,MAAM,CAAC;YACjB,IAAI,EAAE,IAAI;YACV,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,QAAQ,CACd,UAAU,EACV,OAAO,CAAC,QAAQ,CACnB;YACD,MAAM,EAAE,0BAA0B;SACrC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC,CAAC;AACR,CAAC"}
1
+ {"version":3,"file":"parcellaire-express.js","sourceRoot":"","sources":["../../src/gpf/parcellaire-express.js"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,QAAQ,CAAC;AAEvB,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC,wCAAwC;AACxC,kFAAkF;AAElF,MAAM,CAAC,MAAM,0BAA0B,GAAG,2DAA2D,CAAC;AACtG,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACrC,gBAAgB;IAChB,SAAS;IACT,SAAS;IACT,UAAU;IACV,qBAAqB;IACrB,YAAY;CACf,CAAC;AAEF;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAK;IAC3B,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAE3E,MAAM,MAAM,GAAG,EAAE,CAAC;IAClB,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,KAAM,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC9B,IAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACzB,SAAS;QACb,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAGD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,SAAS;IACrE,MAAM,CAAC,IAAI,CAAC,yBAAyB,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC;IACxD,kFAAkF;IAClF,MAAM,UAAU,GAAG,sBAAsB,GAAG,IAAI,GAAG,cAAc,CAAC;IAElE,MAAM,UAAU,GAAG;QACf,MAAM,EAAE,OAAO;QACf,aAAa,EAAE,CAAC,GAAG,EAAC,GAAG,CAAC;KAC3B,CAAC;IAEF,uDAAuD;IACvD,MAAM,GAAG,GAAG,4BAA4B,GAAG,IAAI,eAAe,CAAC;QAC3D,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,YAAY;QACrB,QAAQ,EAAE,yBAAyB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,wCAAwC,IAAI,EAAE,CAAA,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACtH,YAAY,EAAE,kBAAkB;QAChC,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEd,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;IAC1G,CAAC;IACD,OAAO,gBAAgB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC/D,0CAA0C;QAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,wCAAwC;QACxC,OAAO,MAAM,CAAC,MAAM,CAAC;YACjB,IAAI,EAAE,IAAI;YACV,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,QAAQ,CACd,UAAU,EACV,OAAO,CAAC,QAAQ,CACnB;YACD,MAAM,EAAE,0BAA0B;SACrC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC,CAAC;AACR,CAAC"}
@@ -3,16 +3,18 @@
3
3
  *
4
4
  * @param {number} lon
5
5
  * @param {number} lat
6
+ * @param {(url: string) => Promise<any>} [fetcher]
6
7
  * @returns
7
8
  */
8
- export function getUrbanisme(lon: number, lat: number): Promise<any>;
9
+ export function getUrbanisme(lon: number, lat: number, fetcher?: (url: string) => Promise<any>): Promise<any>;
9
10
  /**
10
11
  * Get SUP infos for a given location
11
12
  *
12
13
  * @param {number} lon
13
14
  * @param {number} lat
15
+ * @param {(url: string) => Promise<any>} [fetcher]
14
16
  * @returns
15
17
  */
16
- export function getAssiettesServitudes(lon: number, lat: number): Promise<any>;
18
+ export function getAssiettesServitudes(lon: number, lat: number, fetcher?: (url: string) => Promise<any>): Promise<any>;
17
19
  export const URBANISME_TYPES: string[];
18
20
  export const URBANISME_SOURCE: "G\u00E9oplateforme - (WFS G\u00E9oportail de l'Urbanisme)";
@@ -15,14 +15,33 @@ export const URBANISME_TYPES = [
15
15
  'wfs_du:prescription_surf'
16
16
  ];
17
17
  export const URBANISME_SOURCE = "Géoplateforme - (WFS Géoportail de l'Urbanisme)";
18
+ const URBANISME_INVALID_COLLECTION_ERROR = "Le service Urbanisme n'a pas retourné de collection d'objets exploitable";
19
+ const URBANISME_EXCLUDED_PROPERTIES = new Set([
20
+ 'gpu_status',
21
+ 'urlfic'
22
+ ]);
23
+ function sanitizeUrbanismeItem(item) {
24
+ const sanitized = {};
25
+ for (const [key, value] of Object.entries(item)) {
26
+ if (URBANISME_EXCLUDED_PROPERTIES.has(key)) {
27
+ continue;
28
+ }
29
+ if (value === null || value === '') {
30
+ continue;
31
+ }
32
+ sanitized[key] = value;
33
+ }
34
+ return sanitized;
35
+ }
18
36
  /**
19
37
  * Get urbanism infos for a given location
20
38
  *
21
39
  * @param {number} lon
22
40
  * @param {number} lat
41
+ * @param {(url: string) => Promise<any>} [fetcher]
23
42
  * @returns
24
43
  */
25
- export async function getUrbanisme(lon, lat) {
44
+ export async function getUrbanisme(lon, lat, fetcher = fetchJSON) {
26
45
  logger.info(`getUrbanisme(${lon},${lat})...`);
27
46
  // note that EPSG:4326 means lat,lon order for GeoServer -> flipped coordinates...
28
47
  const cql_filter = `DWITHIN(the_geom,Point(${lat} ${lon}),30,meters)`;
@@ -38,17 +57,21 @@ export async function getUrbanisme(lon, lat) {
38
57
  outputFormat: 'application/json',
39
58
  cql_filter: cql_filter
40
59
  }).toString();
41
- const featureCollection = await fetchJSON(url);
60
+ const featureCollection = await fetcher(url);
61
+ if (!Array.isArray(featureCollection?.features)) {
62
+ throw new Error(URBANISME_INVALID_COLLECTION_ERROR);
63
+ }
42
64
  return featureCollection.features.map((feature) => {
43
65
  // parse type from id (ex: "commune.3837")
44
66
  const type = feature.id.split('.')[0];
45
67
  // ignore geometry and extend properties
46
- return Object.assign({
68
+ const item = Object.assign({
47
69
  type: type,
48
70
  id: feature.id,
49
71
  bbox: feature.bbox,
50
72
  distance: (distance(sourceGeom, feature.geometry) * 1000.0)
51
73
  }, feature.properties);
74
+ return sanitizeUrbanismeItem(item);
52
75
  });
53
76
  }
54
77
  const ASSIETTES_SUP_TYPES = [
@@ -61,9 +84,10 @@ const ASSIETTES_SUP_TYPES = [
61
84
  *
62
85
  * @param {number} lon
63
86
  * @param {number} lat
87
+ * @param {(url: string) => Promise<any>} [fetcher]
64
88
  * @returns
65
89
  */
66
- export async function getAssiettesServitudes(lon, lat) {
90
+ export async function getAssiettesServitudes(lon, lat, fetcher = fetchJSON) {
67
91
  logger.info(`getAssiettesServitudes(${lon},${lat})...`);
68
92
  // note that EPSG:4326 means lat,lon order for GeoServer -> flipped coordinates...
69
93
  const cql_filter = `DWITHIN(the_geom,Point(${lat} ${lon}),30,meters)`;
@@ -79,7 +103,10 @@ export async function getAssiettesServitudes(lon, lat) {
79
103
  outputFormat: 'application/json',
80
104
  cql_filter: cql_filter
81
105
  }).toString();
82
- const featureCollection = await fetchJSON(url);
106
+ const featureCollection = await fetcher(url);
107
+ if (!Array.isArray(featureCollection?.features)) {
108
+ throw new Error(URBANISME_INVALID_COLLECTION_ERROR);
109
+ }
83
110
  return featureCollection.features.map((feature) => {
84
111
  // parse type from id (ex: "commune.3837")
85
112
  const type = feature.id.split('.')[0];
@@ -1 +1 @@
1
- {"version":3,"file":"urbanisme.js","sourceRoot":"","sources":["../../src/gpf/urbanisme.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"urbanisme.js","sourceRoot":"","sources":["../../src/gpf/urbanisme.js"],"names":[],"mappings":"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"}
package/dist/gpf/wfs.d.ts CHANGED
@@ -1,17 +1,19 @@
1
- import { Collection, MiniSearchCollectionSearchEngineOptions } from '@ignfab/gpf-schema-store';
1
+ import { Collection, MiniSearchCollectionSearchOptions } from '@ignfab/gpf-schema-store';
2
2
  export declare const GPF_WFS_URL = "https://data.geopf.fr/wfs";
3
+ type MiniSearchOptions = MiniSearchCollectionSearchOptions;
3
4
  export declare class FeatureTypeNotFoundError extends Error {
4
5
  constructor(name: string);
5
6
  }
6
- export declare function loadSearchOptionsFromEnv(): MiniSearchCollectionSearchEngineOptions | undefined;
7
+ export declare function loadMiniSearchOptionsFromEnv(): MiniSearchOptions | undefined;
7
8
  export declare class WfsClient {
8
9
  baseUrl: string;
9
10
  private readonly catalog;
10
11
  constructor(baseUrl?: string, options?: {
11
- search?: MiniSearchCollectionSearchEngineOptions;
12
+ miniSearch?: MiniSearchOptions;
12
13
  });
13
14
  getFeatureTypes(): Promise<Collection[]>;
14
15
  searchFeatureTypes(query: string, maxResults?: number): Promise<Collection[]>;
15
16
  getFeatureType(name: string): Promise<Collection>;
16
17
  }
17
18
  export declare const wfsClient: WfsClient;
19
+ export {};
package/dist/gpf/wfs.js CHANGED
@@ -2,22 +2,28 @@
2
2
  import { getCollectionCatalog, MiniSearchCollectionSearchEngine, } from '@ignfab/gpf-schema-store';
3
3
  // --- Constants ---
4
4
  export const GPF_WFS_URL = "https://data.geopf.fr/wfs";
5
- // Environment variable used to inject search engine options at runtime (JSON string).
6
- const GPF_WFS_SEARCH_OPTIONS_ENV = "GPF_WFS_SEARCH_OPTIONS";
5
+ // Environment variable used to inject MiniSearch options at runtime (JSON string).
6
+ const GPF_WFS_MINISEARCH_OPTIONS_ENV = "GPF_WFS_MINISEARCH_OPTIONS";
7
7
  // Keys accepted at the top level of the search options object.
8
- const TOP_LEVEL_SEARCH_OPTION_KEYS = ["fuzzy", "boost"];
9
- // Keys accepted inside the nested `boost` object.
10
- const BOOST_SEARCH_OPTION_KEYS = [
8
+ const TOP_LEVEL_MINISEARCH_OPTION_KEYS = ["fields", "combineWith", "fuzzy", "boost"];
9
+ // Shared keys used by both `fields` and `boost` in MiniSearchCollectionSearchOptions.
10
+ const MINISEARCH_INDEXED_OPTION_KEYS = [
11
11
  "namespace",
12
12
  "name",
13
13
  "title",
14
14
  "description",
15
15
  "properties",
16
+ "enums",
17
+ "identifierTokens",
16
18
  ];
19
+ const MINISEARCH_FIELD_OPTION_KEYS = MINISEARCH_INDEXED_OPTION_KEYS;
20
+ const MINISEARCH_COMBINE_WITH_VALUES = ["AND", "OR"];
21
+ const MINISEARCH_BOOST_OPTION_KEYS = MINISEARCH_INDEXED_OPTION_KEYS;
17
22
  // --- Errors ---
18
23
  export class FeatureTypeNotFoundError extends Error {
19
24
  constructor(name) {
20
- super(`Type '${name}' not found`);
25
+ super(`Le type '${name}' est introuvable`);
26
+ this.name = "FeatureTypeNotFoundError";
21
27
  }
22
28
  }
23
29
  // --- Helpers ---
@@ -28,21 +34,47 @@ function isFiniteNumber(value) {
28
34
  return typeof value === "number" && Number.isFinite(value);
29
35
  }
30
36
  function invalidSearchOptionsError(reason) {
31
- return new Error(`Invalid ${GPF_WFS_SEARCH_OPTIONS_ENV}: ${reason}`);
37
+ return new Error(`Invalid ${GPF_WFS_MINISEARCH_OPTIONS_ENV}: ${reason}`);
32
38
  }
33
39
  // --- Search options parsing ---
34
- // Parses and validates a plain-object value into MiniSearchCollectionSearchEngineOptions.
40
+ // Parses and validates a plain-object value into MiniSearchCollectionSearchOptions.
35
41
  // Throws a descriptive error if the value has unexpected keys or wrong value types.
36
- function parseSearchOptions(value) {
42
+ function parseMiniSearchOptions(value) {
37
43
  if (!isPlainObject(value)) {
38
44
  throw invalidSearchOptionsError("expected a JSON object");
39
45
  }
40
46
  for (const key of Object.keys(value)) {
41
- if (!TOP_LEVEL_SEARCH_OPTION_KEYS.includes(key)) {
47
+ if (!TOP_LEVEL_MINISEARCH_OPTION_KEYS.includes(key)) {
42
48
  throw invalidSearchOptionsError(`unexpected key '${key}'`);
43
49
  }
44
50
  }
45
51
  const options = {};
52
+ if (value.fields !== undefined) {
53
+ if (!Array.isArray(value.fields)) {
54
+ throw invalidSearchOptionsError("expected 'fields' to be an array");
55
+ }
56
+ const fields = [];
57
+ for (const field of value.fields) {
58
+ if (typeof field !== "string") {
59
+ throw invalidSearchOptionsError("expected every 'fields' item to be a string");
60
+ }
61
+ if (!MINISEARCH_FIELD_OPTION_KEYS.includes(field)) {
62
+ throw invalidSearchOptionsError(`unexpected value 'fields.${field}'`);
63
+ }
64
+ fields.push(field);
65
+ }
66
+ options.fields = fields;
67
+ }
68
+ if (value.combineWith !== undefined) {
69
+ if (typeof value.combineWith !== "string") {
70
+ throw invalidSearchOptionsError("expected 'combineWith' to be a string");
71
+ }
72
+ const combineWith = value.combineWith;
73
+ if (!MINISEARCH_COMBINE_WITH_VALUES.includes(combineWith)) {
74
+ throw invalidSearchOptionsError("expected 'combineWith' to be 'AND' or 'OR'");
75
+ }
76
+ options.combineWith = combineWith;
77
+ }
46
78
  if (value.fuzzy !== undefined) {
47
79
  if (!isFiniteNumber(value.fuzzy)) {
48
80
  throw invalidSearchOptionsError("expected 'fuzzy' to be a finite number");
@@ -55,7 +87,7 @@ function parseSearchOptions(value) {
55
87
  }
56
88
  const boost = {};
57
89
  for (const key of Object.keys(value.boost)) {
58
- if (!BOOST_SEARCH_OPTION_KEYS.includes(key)) {
90
+ if (!MINISEARCH_BOOST_OPTION_KEYS.includes(key)) {
59
91
  throw invalidSearchOptionsError(`unexpected key 'boost.${key}'`);
60
92
  }
61
93
  const rawScore = value.boost[key];
@@ -68,10 +100,18 @@ function parseSearchOptions(value) {
68
100
  }
69
101
  return options;
70
102
  }
71
- // Reads search options from the GPF_WFS_SEARCH_OPTIONS environment variable.
103
+ function createMiniSearchEngineOptions(miniSearch) {
104
+ if (!miniSearch) {
105
+ return undefined;
106
+ }
107
+ return {
108
+ defaultSearchOptions: miniSearch,
109
+ };
110
+ }
111
+ // Reads MiniSearch options from the GPF_WFS_MINISEARCH_OPTIONS environment variable.
72
112
  // Returns undefined when the variable is absent or empty.
73
- export function loadSearchOptionsFromEnv() {
74
- const rawValue = process.env[GPF_WFS_SEARCH_OPTIONS_ENV];
113
+ export function loadMiniSearchOptionsFromEnv() {
114
+ const rawValue = process.env[GPF_WFS_MINISEARCH_OPTIONS_ENV];
75
115
  if (!rawValue || rawValue.trim() === "") {
76
116
  return undefined;
77
117
  }
@@ -83,7 +123,7 @@ export function loadSearchOptionsFromEnv() {
83
123
  const reason = error instanceof Error ? error.message : "unknown JSON parse error";
84
124
  throw invalidSearchOptionsError(`expected valid JSON (${reason})`);
85
125
  }
86
- return parseSearchOptions(parsedValue);
126
+ return parseMiniSearchOptions(parsedValue);
87
127
  }
88
128
  // --- WFS client ---
89
129
  export class WfsClient {
@@ -91,8 +131,9 @@ export class WfsClient {
91
131
  catalog;
92
132
  constructor(baseUrl = GPF_WFS_URL, options = {}) {
93
133
  this.baseUrl = baseUrl;
134
+ const searchEngineOptions = createMiniSearchEngineOptions(options.miniSearch);
94
135
  this.catalog = getCollectionCatalog({
95
- engineFactory: (items) => new MiniSearchCollectionSearchEngine(items, options.search),
136
+ engineFactory: (items) => new MiniSearchCollectionSearchEngine(items, searchEngineOptions),
96
137
  });
97
138
  }
98
139
  async getFeatureTypes() {
@@ -101,7 +142,6 @@ export class WfsClient {
101
142
  async searchFeatureTypes(query, maxResults = 20) {
102
143
  return this.catalog.search(query, {
103
144
  limit: maxResults,
104
- combineWith: 'AND',
105
145
  });
106
146
  }
107
147
  async getFeatureType(name) {
@@ -113,8 +153,8 @@ export class WfsClient {
113
153
  }
114
154
  }
115
155
  // --- Default singleton ---
116
- // Pre-configured client using the default GPF endpoint and optional env-based search options.
156
+ // Pre-configured client using the default GPF endpoint and optional env-based MiniSearch options.
117
157
  export const wfsClient = new WfsClient(undefined, {
118
- search: loadSearchOptionsFromEnv(),
158
+ miniSearch: loadMiniSearchOptionsFromEnv(),
119
159
  });
120
160
  //# sourceMappingURL=wfs.js.map