diff --git a/CHANGELOG.md b/CHANGELOG.md index e379ad6..68e2cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,44 @@ +## 6.1.5 + +### [Fixed] + +* Nettoyage et bascule des styles et TMS lors d'une extinction ou redémarrage du serveur +* `WMTS` + * pour que la couche apparaisse dans les capacités Inspire, le style par défaut doit avoir l'identifiant `.Default` +* `WMS` + * pour que la couche apparaisse dans les capacités Inspire, le style par défaut doit avoir l'identifiant `.Default` + * on vérifie que la donnée a bien 3 canaux pour répondre une tuile en JPEG + * Dans le cas d'une couche inconnu, le code est `LayerNotDefined` + * Écriture de la valeur de nodata dans les header geotiff +* `TMS` + * Dans la description des couches, les niveaux (`TileSet`) doivent être définis dans la balise `TileSets` et non `TileMap` et `Origin` est le coin en bas à gauche et non en haut à gauche + ## 6.1.4 ### [Fixed] * `WMS` - * Correction de l'attribution dans les capacités : contenu du logo et ordre des balises - * Correction du géoréférencement dans les réponses GeoTIFF + * Correction de l'attribution dans les capacités : contenu du logo et ordre des balises + * Correction du géoréférencement dans les réponses GeoTIFF * `TMS` - * Correction de l'attribution dans les capacités : contenu du logo - * Ajout du header `Content-Encoding: defalte` pour les tuiles bil compressées + * Correction de l'attribution dans les capacités : contenu du logo + * Ajout du header `Content-Encoding: deflate` pour les tuiles bil compressées * `WMTS` - * Correction des balises de métadonnées dans les capacités - * Ajout du header `Content-Encoding: defalte` pour les tuiles bil compressées + * Correction des balises de métadonnées dans les capacités + * Ajout du header `Content-Encoding: deflate` pour les tuiles bil compressées ## 6.1.3 ### [Fixed] * `WMTS` : - * correction du calcul des tuiles limites pour le TMS natif lorsque les limites de la couche sont surchargées par une bbox - * pour que la couche apparaisse dans les capacités Inspire, le format de la tuile doit être le PNG - * dans les capacités inspire, `inspire_vs:ExtendedCapabilities` doit être dans `OperationsMetadata` + * correction du calcul des tuiles limites pour le TMS natif lorsque les limites de la couche sont surchargées par une bbox + * pour que la couche apparaisse dans les capacités Inspire, le format de la tuile doit être le PNG + * dans les capacités inspire, `inspire_vs:ExtendedCapabilities` doit être dans `OperationsMetadata` * `WMS` - * Ajout des schémas XML dans les réponses en erreur + * Ajout des schémas XML dans les réponses en erreur ## 6.1.2 @@ -98,24 +113,24 @@ ### [Changed] * WMTS : - * On ajoute à la liste des TMS au niveau du getcapabilities les TMS "de base" en entier + * On ajoute à la liste des TMS au niveau du getcapabilities les TMS "de base" en entier ## 5.4.1 ### [Added] * WMS : - * Ajout de la configuration du titre et nom de la couche racine dans le getCapabilities + * Ajout de la configuration du titre et nom de la couche racine dans le getCapabilities ### [Changed] * WMS : - * Déplacement des fonctions d'écriture d'une bbox du getCapabilities dans UtilsXML - * Ajout de bbox au niveau de la couche racine dans le getCapabilities - * L'attribution d'une couche est mise après les éventuelles métadonnées + * Déplacement des fonctions d'écriture d'une bbox du getCapabilities dans UtilsXML + * Ajout de bbox au niveau de la couche racine dans le getCapabilities + * L'attribution d'une couche est mise après les éventuelles métadonnées * WMTS : - * On ne liste plus les couches de tuiles vectorielles dans le getCapabilities - * On exporte dans le getcapabilities un TMS différent pour chaque couple haut / bas présent dans les couches + * On ne liste plus les couches de tuiles vectorielles dans le getCapabilities + * On exporte dans le getcapabilities un TMS différent pour chaque couple haut / bas présent dans les couches ## 5.3.0 @@ -162,25 +177,19 @@ Implémentation partielle de l'API OGC Tiles - Part 1 [v1.0.0 final release](htt ### [Added] * Liste de nouvelles routes pour obtenir le **GetCapabilities**: - * /tiles/collections - avec les paramètres facultatifs : - * bbox - * limit + * /tiles/collections avec les paramètres facultatifs : + * bbox + * limit * /tiles/collections/{layer}/map/tiles * /tiles/collections/{layer}/tiles * /tiles/tilematrixsets * /tiles/tilematrixsets/{tms} - * Liste des nouvelles routes pour obtenir le **GetTile** : - - * Raster - * /tiles/map/tiles/{tms}/{level}/{row}/{col} - avec le paramètre obligatoire : collections={layer} - * /tiles/styles/{style}/map/tiles/{tms}/{level}/{row}/{col} - avec le paramètre obligatoire : collections={layer} - * /tiles/collections/{layer}/styles/{style}/map/tiles/{tms}/{level}/{row}/{col} - * /tiles/collections/{layer}/map/tiles/{tms}/{level}/{row}/{col} - + * Raster + * /tiles/map/tiles/{tms}/{level}/{row}/{col} avec le paramètre obligatoire : collections={layer} + * /tiles/styles/{style}/map/tiles/{tms}/{level}/{row}/{col} avec le paramètre obligatoire : collections={layer} + * /tiles/collections/{layer}/styles/{style}/map/tiles/{tms}/{level}/{row}/{col} + * /tiles/collections/{layer}/map/tiles/{tms}/{level}/{row}/{col} * Vecteur * /tiles/tiles/{tms}/{level}/{row}/{col}?collections={layer} * /tiles/collections/{layer}/tiles/{tms}/{level}/{row}/{col} @@ -227,10 +236,10 @@ Les configurations des couches, styles et tile matrix sets peuvent être des obj ### [Added] * Implémentation de routes de santé - * `/healthcheck` : informations générales, version, date de lancement, statut général - * `/healthcheck/info` : informations détaillées, listes de couches, styles et tile matrix sets - * `/healthcheck/depends` : informations sur les stockages, nombres de contextes par type - * `/healthcheck/threads` : informations sur les threads, statut, requêtes prises en charge, dernier temps de réponse + * `/healthcheck` : informations générales, version, date de lancement, statut général + * `/healthcheck/info` : informations détaillées, listes de couches, styles et tile matrix sets + * `/healthcheck/depends` : informations sur les stockages, nombres de contextes par type + * `/healthcheck/threads` : informations sur les threads, statut, requêtes prises en charge, dernier temps de réponse ### [Fixed] diff --git a/README.md b/README.md index b0c3836..eda8ec0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Serveur de diffusion WMS, WMTS et TMS -![ROK4 Logo](https://rok4.github.io/assets/images/rok4.png) +![ROK4 Logo](https://rok4.github.io/assets/images/rok4-256.png) Le serveur implémente les standards ouverts de l’Open Geospatial Consortium (OGC) WMS 1.3.0, WMTS 1.0.0 et OGC API Tiles 1.0.0, ainsi que le TMS (Tile Map Service). Il vise deux objectifs principaux : @@ -224,7 +224,7 @@ Lorsqu'une couche est chargée, les descripteurs de pyramide, de TMS et de style Pour les TMS et les styles, ils sont cherchés dans les répertoires (fichier ou objet) renseignés dans le `server.json`, avec comme nom de fichier objet `.json`. Un annuaire est tenu à jour pour ne charger qu'une seule fois le style ou le TMS. -![Chargement du serveur](./docs/images/rok4server-layer-pyramid.png) +![Chargement du serveur](./docs/images/rok4-server-loading.png) ### Personnalisation des points d'accès aux services diff --git a/docs/images/rok4-server-loading.png b/docs/images/rok4-server-loading.png new file mode 100644 index 0000000..10c29c8 Binary files /dev/null and b/docs/images/rok4-server-loading.png differ diff --git a/docs/images/rok4server-layer-pyramid.png b/docs/images/rok4server-layer-pyramid.png deleted file mode 100644 index cde7a42..0000000 Binary files a/docs/images/rok4server-layer-pyramid.png and /dev/null differ diff --git a/src/Inspire.cpp b/src/Inspire.cpp index 2e62abc..b48c00a 100644 --- a/src/Inspire.cpp +++ b/src/Inspire.cpp @@ -153,8 +153,8 @@ bool is_inspire_wmts ( Layer* layer ) { } // Pour être inspire, le style par défaut doit avoir le bon identifiant - if (layer->get_default_style()->get_identifier() != "inspire_common:DEFAULT") { - BOOST_LOG_TRIVIAL(debug) << "Non conforme INSPIRE WMTS (" << layer->get_id() << ") : style par défaut != inspire_common:DEFAULT" ; + if (layer->get_default_style()->get_identifier() != layer->get_id() + ":Default") { + BOOST_LOG_TRIVIAL(debug) << "Non conforme INSPIRE WMTS (" << layer->get_id() << ") : style par défaut != " + layer->get_id() + ":Default" ; return false; } @@ -174,8 +174,8 @@ bool is_inspire_wms ( Layer* layer ) { } // Pour être inspire, le style par défaut doit avoir le bon identifiant - if (layer->get_default_style()->get_identifier() != "inspire_common:DEFAULT") { - BOOST_LOG_TRIVIAL(debug) << "Non conforme INSPIRE WMS (" << layer->get_id() << ") : style par défaut != inspire_common:DEFAULT" ; + if (layer->get_default_style()->get_identifier() != layer->get_id() + ":Default") { + BOOST_LOG_TRIVIAL(debug) << "Non conforme INSPIRE WMS (" << layer->get_id() << ") : style par défaut != " + layer->get_id() + ":Default" ; return false; } diff --git a/src/configurations/Layer.cpp b/src/configurations/Layer.cpp index 7c3cfd7..dc98a70 100644 --- a/src/configurations/Layer.cpp +++ b/src/configurations/Layer.cpp @@ -1289,14 +1289,14 @@ std::string Layer::get_description_tms(TmsService* service) { TileMatrix* tm = pyramid->get_lowest_level()->get_tm(); root.add("Origin..x", tm->get_x0() ); - root.add("Origin..y", tm->get_y0() ); + root.add("Origin..y", tm->get_y0() - tm->get_matrix_height() * tm->get_tile_height() * tm->get_res() ); root.add("TileFormat..width", tm->get_tile_width() ); root.add("TileFormat..height", tm->get_tile_height() ); root.add("TileFormat..mime-type", Rok4Format::to_mime_type ( pyramid->get_format() ) ); root.add("TileFormat..extension", Rok4Format::to_extension ( pyramid->get_format() ) ); - ptree& tilesets_node = root.add("TileMap", ""); + ptree& tilesets_node = root.add("TileSets", ""); tilesets_node.add(".profile", "none"); int order = 0; diff --git a/src/main.cpp b/src/main.cpp index 6119029..d700b54 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -240,6 +240,10 @@ void reload_configuration ( int signum ) { reload = true; std::cout<< "Rechargement du serveur rok4" << "["<< getpid() <<"]" <set_fcgi_socket ( sock ); } @@ -379,16 +381,21 @@ int main ( int argc, char** argv ) { rok4server_instance->run(signal_pending); - TmsBook::send_to_trash(); - StyleBook::send_to_trash(); if ( reload ) { // Rechargement du serveur BOOST_LOG_TRIVIAL(info) << "Configuration reload" ; sock = rok4server_instance->get_fcgi_socket(); + // Lors du rechargement on a mis à la poubelle tous les anciens TMS et styles + // On peut maintenant les supprimer + TmsBook::empty_trash(); + StyleBook::empty_trash(); } else { // Extinction du serveur BOOST_LOG_TRIVIAL(info) << "Server shutdown" ; + // On va vouloir supprimer tous les styles et TMS, on les envoie donc à la poubelle + TmsBook::send_to_trash(); + StyleBook::send_to_trash(); } delete rok4server_instance; diff --git a/src/services/tiles/gettile.cpp b/src/services/tiles/gettile.cpp index be9bd40..31e42ec 100644 --- a/src/services/tiles/gettile.cpp +++ b/src/services/tiles/gettile.cpp @@ -232,25 +232,29 @@ DataStream* TilesService::get_tile ( Request* req, Rok4Server* serv, bool is_map else if (format == "tiff") { bool is_geotiff = true; + // Dans le cas d'un geotiff, on renseigne la valeur de nodata + // on ne peut mettre qu'une valeur, ce sera celle du premier canal + int nodata = *(layer->get_pyramid()->get_nodata_value()); + // La donnée ne peut être retournée que dans le format de la pyramide source utilisée switch (layer->get_pyramid()->get_format()) { case Rok4Format::TIFF_RAW_UINT8: - return new TiffRawEncoder(image, is_geotiff); + return new TiffRawEncoder(image, is_geotiff, nodata); case Rok4Format::TIFF_LZW_UINT8: - return new TiffLZWEncoder(image, is_geotiff); + return new TiffLZWEncoder(image, is_geotiff, nodata); case Rok4Format::TIFF_ZIP_UINT8: - return new TiffDeflateEncoder(image, is_geotiff); + return new TiffDeflateEncoder(image, is_geotiff, nodata); case Rok4Format::TIFF_PKB_UINT8: - return new TiffPackBitsEncoder(image, is_geotiff); + return new TiffPackBitsEncoder(image, is_geotiff, nodata); case Rok4Format::TIFF_RAW_FLOAT32: - return new TiffRawEncoder(image, is_geotiff); + return new TiffRawEncoder(image, is_geotiff, nodata); case Rok4Format::TIFF_LZW_FLOAT32: - return new TiffLZWEncoder(image, is_geotiff); + return new TiffLZWEncoder(image, is_geotiff, nodata); case Rok4Format::TIFF_ZIP_FLOAT32: - return new TiffDeflateEncoder(image, is_geotiff); + return new TiffDeflateEncoder(image, is_geotiff, nodata); case Rok4Format::TIFF_PKB_FLOAT32: - return new TiffPackBitsEncoder(image, is_geotiff); + return new TiffPackBitsEncoder(image, is_geotiff, nodata); default: delete image; throw TilesException::get_error_message("ResourceNotFound", "Not data found", 404); diff --git a/src/services/wms/getfeatureinfo.cpp b/src/services/wms/getfeatureinfo.cpp index 05083a5..54124b9 100644 --- a/src/services/wms/getfeatureinfo.cpp +++ b/src/services/wms/getfeatureinfo.cpp @@ -69,12 +69,12 @@ DataStream* WmsService::get_feature_info ( Request* req, Rok4Server* serv ) { if (contain_chars(vector_layers.at(i), "<>")) { BOOST_LOG_TRIVIAL(warning) << "Forbidden char detected in WMS layer: " << vector_layers.at(i); - throw WmsException::get_error_message("Layer unknown", "InvalidParameterValue", 400); + throw WmsException::get_error_message("Layer unknown", "LayerNotDefined", 400); } Layer* layer = serv->get_server_configuration()->get_layer(vector_layers.at(i)); if (layer == NULL || ! layer->is_wms_enabled()) { - throw WmsException::get_error_message("Layer " + vector_layers.at(i) + " unknown", "InvalidParameterValue", 400); + throw WmsException::get_error_message("Layer " + vector_layers.at(i) + " unknown", "LayerNotDefined", 400); } layers.push_back ( layer ); diff --git a/src/services/wms/getmap.cpp b/src/services/wms/getmap.cpp index c8b81b8..8d2e27f 100644 --- a/src/services/wms/getmap.cpp +++ b/src/services/wms/getmap.cpp @@ -82,12 +82,12 @@ DataStream* WmsService::get_map ( Request* req, Rok4Server* serv ) { if (contain_chars(vector_layers.at(i), "<>")) { BOOST_LOG_TRIVIAL(warning) << "Forbidden char detected in WMS layer: " << vector_layers.at(i); - throw WmsException::get_error_message("Layer unknown", "InvalidParameterValue", 400); + throw WmsException::get_error_message("Layer unknown", "LayerNotDefined", 400); } Layer* layer = serv->get_server_configuration()->get_layer(vector_layers.at(i)); if (layer == NULL || ! layer->is_wms_enabled()) { - throw WmsException::get_error_message("Layer " + vector_layers.at(i) + " unknown", "InvalidParameterValue", 400); + throw WmsException::get_error_message("Layer " + vector_layers.at(i) + " unknown", "LayerNotDefined", 400); } layers.push_back ( layer ); @@ -253,6 +253,7 @@ DataStream* WmsService::get_map ( Request* req, Rok4Server* serv ) { // Le format des canaux sera identifié à partir des données en entrée, en prenant en compte le style SampleFormat::eSampleFormat sample_format = SampleFormat::UNKNOWN; + int nodata; // Le nombre de canaux dans l'image finale sera égale au nombre maximum dans les données en entrée (en prenant en compte le style) int bands = 0; @@ -271,8 +272,11 @@ DataStream* WmsService::get_map ( Request* req, Rok4Server* serv ) { throw WmsException::get_error_message("All layers (with their style) have to own the same sample format (int or float)", "InvalidParameterValue", 400); } else { sample_format = style->get_sample_format(layers.at(i)->get_pyramid()->get_sample_format()); - } + // Dans le cas d'un geotiff, on renseigne la valeur de nodata + // on ne peut mettre qu'une valeur, ce sera celle du premier canal du nodata de la première couche (après style) + nodata = *(style->get_output_nodata_value(layers.at(i)->get_pyramid()->get_nodata_value())); + } Image* image = layers.at(i)->get_pyramid()->getbbox(max_tile_x, max_tile_y, bbox, width, height, crs, crs_equals, layers.at(i)->get_resampling(), dpi); @@ -317,37 +321,37 @@ DataStream* WmsService::get_map ( Request* req, Rok4Server* serv ) { if (sample_format == SampleFormat::UINT8) { if (opt.compare("lzw") == 0) { - return new TiffLZWEncoder(final_image, is_geotiff); + return new TiffLZWEncoder(final_image, is_geotiff, nodata); } if (opt.compare("deflate") == 0) { - return new TiffDeflateEncoder(final_image, is_geotiff); + return new TiffDeflateEncoder(final_image, is_geotiff, nodata); } if (opt.compare("raw") == 0 || opt == "") { - return new TiffRawEncoder(final_image, is_geotiff); + return new TiffRawEncoder(final_image, is_geotiff, nodata); } if (opt.compare("packbits") == 0) { - return new TiffPackBitsEncoder(final_image, is_geotiff); + return new TiffPackBitsEncoder(final_image, is_geotiff, nodata); } } else if (sample_format == SampleFormat::FLOAT32) { if (opt.compare("lzw") == 0) { - return new TiffLZWEncoder(final_image, is_geotiff); + return new TiffLZWEncoder(final_image, is_geotiff, nodata); } if (opt.compare("deflate") == 0) { - return new TiffDeflateEncoder(final_image, is_geotiff); + return new TiffDeflateEncoder(final_image, is_geotiff, nodata); } if (opt.compare("raw") == 0 || opt == "") { - return new TiffRawEncoder(final_image, is_geotiff); + return new TiffRawEncoder(final_image, is_geotiff, nodata); } if (opt.compare("packbits") == 0) { - return new TiffPackBitsEncoder(final_image, is_geotiff); + return new TiffPackBitsEncoder(final_image, is_geotiff, nodata); } } delete final_image; throw WmsException::get_error_message("Used data and expected format are not consistent", "InvalidParameterValue", 400); } - else if (format == "image/jpeg" && sample_format == SampleFormat::UINT8) { + else if (format == "image/jpeg" && sample_format == SampleFormat::UINT8 && bands == 3) { std::map::iterator it = format_options.find("quality"); int quality = 75; diff --git a/src/services/wmts/gettile.cpp b/src/services/wmts/gettile.cpp index 6bba29b..4a24f17 100644 --- a/src/services/wmts/gettile.cpp +++ b/src/services/wmts/gettile.cpp @@ -203,6 +203,10 @@ DataStream* WmtsService::get_tile(Request* req, Rok4Server* serv) { else if (format == "image/tiff" || format == "image/geotiff") { bool is_geotiff = (format == "image/geotiff"); + // Dans le cas d'un geotiff, on renseigne la valeur de nodata + // on ne peut mettre qu'une valeur, ce sera celle du premier canal + int nodata = *(layer->get_pyramid()->get_nodata_value()); + std::map::iterator it = format_options.find("compression"); std::string opt = ""; if (it != format_options.end()) { @@ -217,16 +221,16 @@ DataStream* WmtsService::get_tile(Request* req, Rok4Server* serv) { case Rok4Format::TIFF_ZIP_UINT8: case Rok4Format::TIFF_PKB_UINT8: if (opt.compare("lzw") == 0) { - return new TiffLZWEncoder(image, is_geotiff); + return new TiffLZWEncoder(image, is_geotiff, nodata); } if (opt.compare("deflate") == 0) { - return new TiffDeflateEncoder(image, is_geotiff); + return new TiffDeflateEncoder(image, is_geotiff, nodata); } if (opt.compare("raw") == 0 || opt == "") { - return new TiffRawEncoder(image, is_geotiff); + return new TiffRawEncoder(image, is_geotiff, nodata); } if (opt.compare("packbits") == 0) { - return new TiffPackBitsEncoder(image, is_geotiff); + return new TiffPackBitsEncoder(image, is_geotiff, nodata); } break; @@ -236,16 +240,16 @@ DataStream* WmtsService::get_tile(Request* req, Rok4Server* serv) { case Rok4Format::TIFF_ZIP_FLOAT32: case Rok4Format::TIFF_PKB_FLOAT32: if (opt.compare("lzw") == 0) { - return new TiffLZWEncoder(image, is_geotiff); + return new TiffLZWEncoder(image, is_geotiff, nodata); } if (opt.compare("deflate") == 0) { - return new TiffDeflateEncoder(image, is_geotiff); + return new TiffDeflateEncoder(image, is_geotiff, nodata); } if (opt.compare("raw") == 0 || opt == "") { - return new TiffRawEncoder(image, is_geotiff); + return new TiffRawEncoder(image, is_geotiff, nodata); } if (opt.compare("packbits") == 0) { - return new TiffPackBitsEncoder(image, is_geotiff); + return new TiffPackBitsEncoder(image, is_geotiff, nodata); } break;