Skip to content

Commit

Permalink
Merge pull request #264 from KNMI/liveupdate
Browse files Browse the repository at this point in the history
Liveupdate layer
  • Loading branch information
maartenplieger committed Feb 14, 2024
2 parents 9c75535 + 7426e6f commit 2ea75a8
Show file tree
Hide file tree
Showing 14 changed files with 165 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
},
"cStandard": "${default}",
"cppStandard": "${default}",
"compilerPath": "${default}"
"compilerPath": "${default}",
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ USER root
LABEL maintainer="adaguc@knmi.nl"

# Version should be same as in Definitions.h
LABEL version="2.17.0"
LABEL version="2.18.0"

# Try to update image packages
RUN apt-get -q -y update \
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
**Version 2.18.0 2024-02-14**
- Support live update layer, displays a GetMap image with the current time per second for the last hour.

**Version 2.17.0 2024-02-14**
- Colors in GetMap images now matches exactly the colors configured in the legend and style in png32 mode
- CSV reader is more flexible
Expand Down
2 changes: 2 additions & 0 deletions adagucserverEC/CDataSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ int CDataSource::setCFGLayer(CServerParams *_srvParams, CServerConfig::XMLE_Conf
dLayerType = CConfigReaderLayerTypeUnknown;
} else if (cfgLayer->attr.type.equals("baselayer")) {
dLayerType = CConfigReaderLayerTypeBaseLayer;
} else if (cfgLayer->attr.type.equals("liveupdate")) {
dLayerType = CConfigReaderLayerTypeLiveUpdate;
} else if (cfgLayer->attr.type.empty() == false) {
if (strlen(cfgLayer->attr.type.c_str()) > 0) {
dLayerType = CConfigReaderLayerTypeUnknown;
Expand Down
2 changes: 2 additions & 0 deletions adagucserverEC/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ add_library(
CXMLSerializerInterface.h
json.c
json.h
LayerTypeLiveUpdate/LayerTypeLiveUpdate.cpp
LayerTypeLiveUpdate/LayerTypeLiveUpdate.h
testadagucserver.cpp
)

Expand Down
13 changes: 12 additions & 1 deletion adagucserverEC/CRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "CSLD.h"
#include "CHandleMetadata.h"
#include "CCreateTiles.h"
#include "LayerTypeLiveUpdate/LayerTypeLiveUpdate.h"
const char *CRequest::className = "CRequest";
int CRequest::CGI = 0;

Expand Down Expand Up @@ -1786,13 +1787,17 @@ int CRequest::process_all_layers() {
dataSources[j]->addStep("", NULL);
// dataSources[j]->getCDFDims()->addDimension("none","0",0);
}
if (dataSources[j]->dLayerType == CConfigReaderLayerTypeLiveUpdate) {
layerTypeLiveUpdateConfigureDimensionsInDataSource(dataSources[j]);
}
}

// Try to find BBOX automatically, when not provided.
if (srvParam->requestType == REQUEST_WMS_GETMAP) {
if (srvParam->dFound_BBOX == 0) {
for (size_t d = 0; d < dataSources.size(); d++) {
if (dataSources[d]->dLayerType != CConfigReaderLayerTypeCascaded && dataSources[d]->dLayerType != CConfigReaderLayerTypeBaseLayer) {
if (dataSources[d]->dLayerType != CConfigReaderLayerTypeCascaded && dataSources[d]->dLayerType != CConfigReaderLayerTypeBaseLayer &&
dataSources[d]->dLayerType != CConfigReaderLayerTypeLiveUpdate) {
CImageWarper warper;
CDataReader reader;
status = reader.open(dataSources[d], CNETCDFREADER_MODE_OPEN_HEADER);
Expand Down Expand Up @@ -2266,6 +2271,12 @@ int CRequest::process_all_layers() {
CDBError("Returning from line %d", i);
return 1;
}
} else if (dataSources[j]->dLayerType == CConfigReaderLayerTypeLiveUpdate) {
// Render the current time in an image for testing purpose / frontend development
CDrawImage image;
layerTypeLiveUpdateRenderIntoDrawImage(&image, srvParam);
printf("%s%c%c\n", "Content-Type:image/png", 13, 10);
image.printImagePng8(true);
} else {
CDBError("Unknown layer type");
}
Expand Down
21 changes: 19 additions & 2 deletions adagucserverEC/CXMLGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <string>
#include "CXMLGen.h"
#include "CDBFactory.h"
#include "LayerTypeLiveUpdate/LayerTypeLiveUpdate.h"
// #define CXMLGEN_DEBUG

const char *CFile::className = "CFile";
Expand Down Expand Up @@ -184,6 +185,13 @@ int CXMLGen::getDataSourceForLayer(WMSLayer *myWMSLayer) {
}
return 0;
}
// This a liveupdate layer
if (myWMSLayer->dataSource->dLayerType == CConfigReaderLayerTypeLiveUpdate) {
#ifdef CXMLGEN_DEBUG
CDBDebug("Live update layer");
#endif
return layerTypeLiveUpdateConfigureWMSLayerForGetCapabilities(myWMSLayer);
}
if (myWMSLayer->fileName.empty()) {
CDBError("No file name specified for layer %s", myWMSLayer->dataSource->layerName.c_str());
return 1;
Expand Down Expand Up @@ -252,6 +260,12 @@ int CXMLGen::getProjectionInformationForLayer(WMSLayer *myWMSLayer) {
}
}

if (myWMSLayer->dataSource->dLayerType == CConfigReaderLayerTypeLiveUpdate) {
if (myWMSLayer->dataSource->cfgLayer->LatLonBox.size() == 0) {
return 0;
}
}

CGeoParams geo;
CImageWarper warper;
int status;
Expand Down Expand Up @@ -718,6 +732,9 @@ int CXMLGen::getStylesForLayer(WMSLayer *myWMSLayer) {
if (myWMSLayer->dataSource->dLayerType == CConfigReaderLayerTypeCascaded) {
return 0;
}
if (myWMSLayer->dataSource->dLayerType == CConfigReaderLayerTypeLiveUpdate) {
return 0;
}

CT::PointerList<CStyleConfiguration *> *styleList = myWMSLayer->dataSource->getStyleListForDataSource(myWMSLayer->dataSource);

Expand Down Expand Up @@ -1735,7 +1752,7 @@ int CXMLGen::OGCGetCapabilities(CServerParams *_srvParam, CT::string *XMLDocumen
if (status != 0) myWMSLayer->hasError = 1;
}

if (myWMSLayer->dataSource->dLayerType == CConfigReaderLayerTypeCascaded) {
if (myWMSLayer->dataSource->dLayerType == CConfigReaderLayerTypeCascaded || myWMSLayer->dataSource->dLayerType == CConfigReaderLayerTypeLiveUpdate) {
myWMSLayer->isQuerable = 0;
if (srvParam->serviceType == SERVICE_WCS) {
myWMSLayer->hasError = true;
Expand All @@ -1756,7 +1773,7 @@ int CXMLGen::OGCGetCapabilities(CServerParams *_srvParam, CT::string *XMLDocumen
// Auto configure styles
if (myWMSLayer->hasError == false) {
if (myWMSLayer->dataSource->cfgLayer->Styles.size() == 0) {
if (myWMSLayer->dataSource->dLayerType != CConfigReaderLayerTypeCascaded) {
if (myWMSLayer->dataSource->dLayerType != CConfigReaderLayerTypeCascaded && myWMSLayer->dataSource->dLayerType != CConfigReaderLayerTypeLiveUpdate) {
#ifdef CXMLGEN_DEBUG
CDBDebug("cfgLayer->attr.type %d", myWMSLayer->dataSource->dLayerType);
#endif
Expand Down
3 changes: 2 additions & 1 deletion adagucserverEC/Definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@
#ifndef Definitions_H
#define Definitions_H

#define ADAGUCSERVER_VERSION "2.17.0" // Please also update in the Dockerfile to the same version
#define ADAGUCSERVER_VERSION "2.18.0" // Please also update in the Dockerfile to the same version

// CConfigReaderLayerType
#define CConfigReaderLayerTypeUnknown 0
#define CConfigReaderLayerTypeDataBase 1
#define CConfigReaderLayerTypeStyled 2
#define CConfigReaderLayerTypeCascaded 3
#define CConfigReaderLayerTypeBaseLayer 4
#define CConfigReaderLayerTypeLiveUpdate 6

// CRequest
// Service options
Expand Down
65 changes: 65 additions & 0 deletions adagucserverEC/LayerTypeLiveUpdate/LayerTypeLiveUpdate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "LayerTypeLiveUpdate.h"

int layerTypeLiveUpdateConfigureDimensionsInDataSource(CDataSource *dataSource) {
// This layer has no dimensions, but we need to add one timestep with data in order to make the next code work.
if (dataSource->requiredDims.size() < 1) {

COGCDims *requiredDim = new COGCDims();
requiredDim->isATimeDimension = true;
requiredDim->name = "time";
requiredDim->netCDFDimName = "time";
// The following values need to be filled in and are updated during XML GetCapabilities generation
requiredDim->uniqueValues.push_back("2020-01-01T00:00:00Z");
requiredDim->uniqueValues.push_back("2020-01-02T00:00:00Z");
requiredDim->value = "2020-01-02T00:00:00Z";
dataSource->requiredDims.push_back(requiredDim);
}
dataSource->addStep("", NULL);
dataSource->getCDFDims()->addDimension("none", "0", 0);
return 0;
}

int layerTypeLiveUpdateRenderIntoDrawImage(CDrawImage *image, CServerParams *srvParam) {
image->enableTransparency(true);
image->setTrueColor(true);
image->createImage(srvParam->Geo);
image->create685Palette();
image->rectangle(0, 0, srvParam->Geo->dWidth, srvParam->Geo->dHeight, CColor(255, 255, 255, 0), CColor(255, 255, 255, 255));
const char *fontFile = image->getFontLocation();
CT::string timeValue = "No time dimension specified";
if (srvParam->requestDims.size() == 1) {
timeValue = srvParam->requestDims[0]->value.c_str();
}
int stepY = 100;
for (int y = 0; y < image->getHeight(); y = y + stepY) {
for (int x = 0; x < image->getWidth(); x = x + 300) {
image->drawText(x + (((y % (stepY * 2)) / stepY) * 150), y, fontFile, 15, 0.1, timeValue.c_str(), CColor(0, 0, 0, 255));
}
}
return 0;
}

int layerTypeLiveUpdateConfigureWMSLayerForGetCapabilities(WMSLayer *myWMSLayer) {
if (myWMSLayer->dataSource->cfgLayer->Title.size() != 0) {
myWMSLayer->title.copy(myWMSLayer->dataSource->cfgLayer->Title[0]->value.c_str());
} else {
myWMSLayer->title.copy(myWMSLayer->dataSource->cfgLayer->Name[0]->value.c_str());
}
CTime timeInstance;
timeInstance.init("seconds since 1970", "standard");
double epochTime = timeInstance.getEpochTimeFromDateString(CTime::currentDateTime());
// CTime::Date cdate = timeInstance.getDate(epochTime);
double startTimeOffset = timeInstance.quantizeTimeToISO8601(epochTime - 3600, "PT1S", "low");
double stopTimeOffset = timeInstance.quantizeTimeToISO8601(epochTime, "PT1S", "low");
CT::string startTime = timeInstance.dateToISOString(timeInstance.offsetToDate(startTimeOffset));
CT::string stopTime = timeInstance.dateToISOString(timeInstance.offsetToDate(stopTimeOffset));
CT::string resTime = "PT1S";
WMSLayer::Dim *dim = new WMSLayer::Dim();
myWMSLayer->dimList.push_back(dim);
dim->name.copy("time");
dim->units.copy("ISO8601");
dim->values.copy(startTime + "/" + stopTime + "/" + resTime);
dim->defaultValue.copy(stopTime.c_str());
dim->hasMultipleValues = true;
return 0;
}
24 changes: 24 additions & 0 deletions adagucserverEC/LayerTypeLiveUpdate/LayerTypeLiveUpdate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "CDataSource.h"
#include "CDrawImage.h"
#include "CServerParams.h"
#include "CServerParams.h"
#include "CXMLGen.h"

#ifndef LAYERTYPELIVEUPDATE_H

/**
* Configures a datasource with a fake time dimension
*/
int layerTypeLiveUpdateConfigureDimensionsInDataSource(CDataSource *dataSource);

/**
* Renders time into an image the based on the time dimension in the GetMap request.
*/
int layerTypeLiveUpdateRenderIntoDrawImage(CDrawImage *image, CServerParams *srvParam);

/**
* Configures an actual time range in a WMSLayer object. This is used for generating the Layer element in the WMS GetCapabilities file
*/
int layerTypeLiveUpdateConfigureWMSLayerForGetCapabilities(WMSLayer *myWMSLayer);

#endif // !LAYERTYPELIVEUPDATE_H
11 changes: 11 additions & 0 deletions data/config/datasets/liveupdatewms.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<!--
See https://dev.knmi.nl/projects/adagucserver/wiki/Dataset, for details
This file can be included by using the adaguc.dataset.cgi?service=wms&DATASET=testdata& key value pair in the URL
-->
<Layer type="liveupdate">
<Name>liveupdate</Name>
</Layer>
</Configuration>

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion doc/configuration/Layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Layer (type,hidden)

Back to [Configuration](./Configuration.md)

- type - The type of layer, can be either database,grid or cascaded
- type - The type of layer, can be either database,grid, liveupdate or cascaded
- hidden - When set to true, the Layer is not advertised in the
GetCapabilities document, but is accessible with GetMap requests.

Expand Down Expand Up @@ -51,3 +51,23 @@ compose nice maps.
<LatLonBox minx="-180" miny="-90" maxx="180" maxy="90"/>
</Layer>
```

Liveupdate layer
---------------

This layer type displays a GetMap image with the current time per second for the last hour.

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<!--
See https://dev.knmi.nl/projects/adagucserver/wiki/Dataset, for details
This file can be included by using the adaguc.dataset.cgi?service=wms&DATASET=testdata& key value pair in the URL
-->
<Layer type="liveupdate">
<Name>liveupdate</Name>
</Layer>
</Configuration>
```

<img src='2024-02-14-liveupdate-layer.png' />
2 changes: 1 addition & 1 deletion doc/developing/scripts/ubuntu_22_install_dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
sudo apt-get update
sudo apt-get install cmake python3-lxml postgresql libcurl4-openssl-dev libcairo2-dev libxml2-dev libgd-dev postgresql-server-dev-all postgresql-client libudunits2-dev udunits-bin g++ m4 netcdf-bin libnetcdf-dev libhdf5-dev libsqlite3-dev python3-netcdf4 python3-dev sqlite3 libsqlite3-dev libwebp-dev libproj-dev proj-bin libproj22
sudo apt-get install cmake python3-lxml postgresql libcurl4-openssl-dev libcairo2-dev libxml2-dev libgd-dev postgresql-server-dev-all postgresql-client libudunits2-dev udunits-bin g++ m4 netcdf-bin libnetcdf-dev libhdf5-dev libsqlite3-dev python3-netcdf4 python3-dev sqlite3 libsqlite3-dev libwebp-dev libproj-dev proj-bin libproj22 libgdal-dev clang-format
```

0 comments on commit 2ea75a8

Please sign in to comment.