geopf-extensions-openlayers
Version:
French Geoportal Extensions for OpenLayers libraries
498 lines (458 loc) • 14 kB
JavaScript
/**
* @classdesc
*
* Gestion du service de recherche de couches
*
* @module Search
* @alias module:~services/Search
* @fixme en attente d'evolution du service pour le filtrage
* @fixme en attente d'evolution du service pour les champs techniques (extra)
* @see https://geoservices.ign.fr/documentation/services/services-geoplateforme/service-geoplateforme-de-recherche
*/
/** resultats du service */
let m_suggestions = [];
/** gestion annulation du fetch */
let controller = new AbortController();
/** index de recherche */
let m_index = "geoplateforme";
/**
* liste des champs de recherche
* valeurs : "title, description, theme, keywords, layer_name"
*/
let m_fields = "title,layer_name";
/** nombre de suggestions du service */
let m_size = "1000";
/** nombre maximum de réponses */
let m_maximumResponses = 10;
/**
* liste des filtres sur les services
* @type {Array}
* @example
* valeurs : ["WMTS", "TMS", "WMS", "WFS", ...]
*/
let m_filterByService = ["WMTS", "TMS"];
/**
* liste des couches à exclure avec ces projections
* @type {Array}
* @example
* ["EPSG:4326",...]
*/
let m_filterByProjection = [];
/**
* liste des couches priortaires dans la recherche
* sous la forme : [name]
* > mettre un poids au score des couches que l'on souhaite
* > mettre en avant dans la recherche
*
* @type {Array}
* @example
* "PLAN.IGN$GEOPORTAIL:GPP:TMS" ou "PLAN.IGN:TMS" ou "PLAN.IGN"
*
*/
let m_filterByLayerPriority = [];
/** Prioriser les couches de type WMTS sur le service WMS */
let m_filterWMTSPriority = false;
/** Filtrer sur les TMS ayant un style */
let m_filterTMS = true;
/** url du service (template avec ${m_index}) */
let m_url = `https://data.geopf.fr/recherche/api/indexes/${m_index}/suggest`;
/**
* Interface pour les evenements
* @example
* target.dispatchEvent(new CustomEvent("myEvent", { detail : {} }));
* target.addEventListener("myEvent", handler);
*/
const target = new EventTarget();
/**
* Appel du service de recherche
* @param {*} text - recherche
* @returns {Object} json
* @fires suggest
* @example
* {
* "attribution": {},
* "srs": [
* "EPSG:3857"
* ],
* "keywords": [],
* "extent": {
* "type": "Polygon",
* "coordinates": [
* [
* [
* 55.9423828,
* -21.5354858
* ],
* [
* 55.9423828,
* 51.1237818
* ],
* [
* -63.28125,
* 51.1237818
* ],
* [
* -63.28125,
* -21.5354858
* ],
* [
* 55.9423828,
* -21.5354858
* ]
* ]
* ]
* },
* "metadata": [],
* "name": "PLAN.IGN",
* "title": "Plan IGN",
* "description": "Plan IGN personnalisable",
* "service": "TMS",
* "url": "https://data.geopf.fr/tms/1.0.0/PLAN.IGN"
* }
*/
const suggest = async (text) => {
// ex. request
// https://data.geopf.fr/recherche/api/indexes/geoplateforme/suggest?text=ORTHO&fields=title
clear();
controller = new AbortController();
let url = new URL(m_url);
let params = {
text : text,
fields : m_fields,
size : m_size
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
var response = await fetch(url, {
// FIXME
// signal : controller.signal
});
var results = await response.json();
if (response.status !== 200) {
throw new Error(response.message);
}
// ex. response
// [
// {
// "index": "geoplateforme",
// "score": 3.4832718,
// "source": {
// "id": "fc2af911-d9c2-4fc8-aee7-46034eebf821",
// "offering_id": "faa4c69c-d03b-4502-af87-7f3667411321",
// "index_name": "geoplateforme",
// "layer_name": "nl_bdtopo_allauch",
// "title": "NL - BD Topo : Allauch",
// "description": "Extrait de BD TOPo sur Allauch",
// "type": "WMS",
// "url": "https://data.geopf.fr/wms-v?service=WMS&version=1.3.0&request=GetMap&layers=nl_bdtopo_allauch&bbox={xmin},{ymin},{xmax},{ymax}&styles={styles}&width={width}&height={height}&srs={srs}&format={format}",
// "open": true,
// "publication_date": "2023-11-27",
// "keywords": [
// "BDTOPO",
// "Recette"
// ],
// "extent": {},
// "metadata_urls": [],
// "srs": [
// "EPSG:2154"
// ],
// "attribution": {
// "title": "Ministère de la Transition écologique et de la Cohésion des territoires",
// "url": "https://www.ecologie.gouv.fr/",
// "logo": {
// "format": "image/png",
// "url": "https://data.geopf.fr/annexes/ressources/logos/mtect.png",
// "width": 294,
// "height": 171
// }
// }
// }
// }
// ]
if (!results || results.length === 0) {
return;
}
// INFO
// Attribution d'un score bonus aux couches priortaires,
// puis retriage des résultats en fonction du score
for (let i = 0; i < results.length; i++) {
const result = results[i];
const found = m_filterByLayerPriority.findIndex((element) => { return element.includes(result.source.layer_name); });
if (found >= 0) {
results[i].score += 100;
// console.log("found", result);
}
}
results.sort((a, b) => b.score - a.score);
// inventaire sur les données ayant plusieurs services associés
var filter = null;
if (m_filterWMTSPriority) {
filter = inventory(results);
}
for (let i = 0; i < results.length; i++) {
const result = results[i];
// filtrage par services
var services = (m_filterByService.length === 0 || m_filterByService.includes(result.source.type));
// FIXME
// utilisation le champ : result.source.open ?
if (services) {
if (unique().length >= m_maximumResponses) {
break;
}
// INFO
// champs possibles mais pas toujours remplis :
// srs[], attributions{}, extent{}, metadata_url[]
var o = {
attribution : result.source.attribution || {},
srs : result.source.srs || [],
keywords : result.source.keywords || [],
extent : result.source.extent || {},
metadata : result.source.metadata_urls || [], // mapping
name : result.source.layer_name || "",
title : result.source.title || "",
description : result.source.description,
service : result.source.type || "", // mapping
url : result.source.url || "",
tech : result.source.tech || {}, // FIXME extra ?
tags : result.source.tags || {},
theme : result.source.theme || "",
producer : result.source.producer || ""
};
// filtrage par projection
if (m_filterByProjection.length) {
// FIXME Array !?
if (m_filterByProjection.includes(o.srs[0])) {
continue;
}
}
// filtrage par priorité du WMTS sur le WMS
if (filter && filter[o.name] && o.service === "WMS") {
continue;
}
// EVOL : en attente des param techniques pour determiner les vrais TMS
// filtrage sur les TMS n'ayant pas de style
if (o.service === "TMS") {
// FIXME
// on part d'une hypothese :
// si la couche TMS a une metadata renseignée au format JSON en 1ere position
// on estime que ceci doit être un style...
o.styles = false;
if (o.metadata.length !== 0 && /(\.json)$/i.test(o.metadata[0])) {
o.styles = true;
}
// Si aucun style et si on décide d'écarter les TMS sans styles
// on n'enregistre pas la donnée...
if (!o.styles && m_filterTMS) {
continue;
}
}
// EVOL : en attente des param techniques pour determiner les faux WMTS
// filtrage des WMTS ayant un TMS vecteur tuilé avec des styles
if (o.service === "WMTS") {
// FIXME
// on part d'une hypothese :
// si la couche WMTS a un style renseigné en metadata
// cela signifie qu'elle a un TMS valide correspondant
if (o.metadata.length !== 0 && /(.json)$/i.test(o.metadata[0]) && m_filterTMS === true) {
continue;
}
}
m_suggestions.push(o);
}
}
target.dispatchEvent(
new CustomEvent("suggest", {
bubbles : true,
detail : getSuggestions()
})
);
return getSuggestions();
};
/**
* Retourne la liste des suggestions sans doublons
* @returns {Object} liste des suggestions sans doublons
*/
const unique = () => {
return m_suggestions.filter((value, index, self) =>
index === self.findIndex((t) => (
t.service === value.service &&
t.name === value.name &&
t.title === value.title &&
t.description === value.description
))
);
// INFO
// soit on trie,
// soit on laisse le trie natif en fonction du score
// .sort((a, b) => {
// // INFO
// // titleA (WMTS)
// // titleA (WMS)
// // titleA (WFS)
// // titleA (TMS)
// // titleB (WMTS)
// return a.title.localeCompare(b.title) || b.service - a.service;
// });
};
/**
* Clear
*/
const clear = () => {
controller.abort();
m_suggestions = [];
};
/**
* Determine si une couche est associé avec des services WMS et/ou WMTS
*
* true : WMTS only ou WMTS avec des WMS associés ou pas
* false : WMS only
* @param {Array} results - réponse de la recherche
* @returns {Object} - ...
* @example
* {
* PLAN.IGN: true, // WMTS et des WMS
* BDTOPO:batiments: false // uniquements des WMS
* }
*/
const inventory = (results) => {
var inventory = {};
for (let i = 0; i < results.length; i++) {
const type = results[i].source.type;
const name = results[i].source.layer_name;
if (type === "WMTS" || type === "WMS") {
if (inventory[name] === undefined) {
inventory[name] = type === "WMTS";
}
inventory[name] ||= type === "WMTS";
}
}
// console.log(inventory);
return inventory;
};
// getter (reponse)
/**
* Liste des suggestions (unique)
* @returns {Array} Liste des suggestions (unique)
*/
const getSuggestions = () => {
return unique();
};
/**
* Liste des noms (unique)
* @returns {Array} Liste des noms (unique)
*/
const getNames = () => {
return unique().map((o) => { return o.name; });
};
/**
* Liste des titres (unique)
* @returns {Array} Liste des titres (unique)
*/
const getTitles = () => {
return unique().map((o) => { return o.title; });
};
// setter (conf)
/**
* Renseigne le nom de l'indexe
* @param {String} value - nom de l'indexe
* @see m_index
*/
const setIndex = (value) => {
m_index = value;
};
/**
* Renseigne la liste des champs de recherche
* @param {Array} value - liste des champs de recherche
* @see m_fields
*/
const setFields = (value) => {
m_fields = value;
};
/**
* Renseigne la liste des suggestions
* @param {Array} value - liste des suggestions
* @see m_suggestions
*/
const setSuggestions = (value) => {
m_suggestions = value;
};
/**
* Renseigne le nombre de suggestions du service
* @param {Number} value - le nombre de suggestions du service
* @see m_size
*/
const setSize = (value) => {
m_size = parseInt(value);
};
/**
* Renseigne l'url du service
* @param {String} value - url du service
* @see m_url
*/
const setUrl = (value) => {
m_url = eval("`" + value + "`"); // insecure !
};
/**
* Renseigne le nombre de réponse souhaitée
* @param {Number} value - nombre de réponse
* @see m_maximumResponses
*/
const setMaximumResponses = (value) => {
m_maximumResponses = parseInt(value);
};
/**
* Filtre sur la liste des services à selectionner
* @param {String} value - liste de service
* @see m_filterByService
*/
const setFiltersByService = (value) => {
m_filterByService = value === "" ? [] : value.split(",");
};
/**
* Filtre sur les couches à exclure
* @param {String} value - liste des projections
* @see m_filterByProjection
*/
const setFiltersByProjection = (value) => {
m_filterByProjection = value === "" ? [] : value.split(",");
};
/**
* Filtre sur les couches prioritaires dans la recherche
* @param {String} value - liste des couches prioritaires
* @see m_filterByLayerPriority
*/
const setFiltersByLayerPriority = (value) => {
m_filterByLayerPriority = value === "" ? [] : value.split(",");
};
/**
* Active ou non le filtre 'strange'
* @param {Boolean} value - active le filtre
*/
const setFilterWMTSPriority = (value) => {
m_filterWMTSPriority = value;
};
/**
* Active ou non le filtre pour ne conserver que les TMS ayant un style
* @param {Boolean} value - active le filtre
*/
const setFilterTMS = (value) => {
m_filterTMS = value;
};
export default {
target,
suggest,
clear,
getSuggestions,
getNames,
getTitles,
setIndex,
setFields,
setSuggestions,
setSize,
setUrl,
setMaximumResponses,
setFiltersByService,
setFiltersByProjection,
setFiltersByLayerPriority,
setFilterWMTSPriority,
setFilterTMS
};