Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIXES #12766] API for timeseries settings #12767

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions geonode/geoserver/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,37 @@ def set_time_info(layer, attribute, end_attribute, presentation, precision_value
gs_catalog.save(resource)


def get_time_info(layer):
"""
Get the time configuration for a layer
"""
time_info = {}
gs_layer = gs_catalog.get_layer(name=layer.name)
if gs_layer is not None:
gs_time_info = gs_layer.resource.metadata.get("time")
if gs_time_info.enabled:
_attr = layer.attributes.filter(attribute=gs_time_info.attribute).first()
time_info["attribute"] = _attr.pk if _attr else None
if gs_time_info.end_attribute is not None:
end_attr = layer.attributes.filter(attribute=gs_time_info.end_attribute).first()
time_info["end_attribute"] = end_attr.pk if end_attr else None
time_info["presentation"] = gs_time_info.presentation
lookup_value = sorted(list(gs_time_info._lookup), key=lambda x: x[1], reverse=True)
if gs_time_info.resolution is not None:
res = gs_time_info.resolution // 1000
for el in lookup_value:
if res % el[1] == 0:
time_info["precision_value"] = res // el[1]
time_info["precision_step"] = el[0]
break
else:
time_info["precision_value"] = gs_time_info.resolution
time_info["precision_step"] = "seconds"
return time_info
else:
return None


ogc_server_settings = OGC_Servers_Handler(settings.OGC_SERVER)["default"]

_wms = None
Expand Down
40 changes: 40 additions & 0 deletions geonode/layers/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,43 @@ class DatasetMetadataSerializer(serializers.Serializer):

class Meta:
fields = "metadata_file"


class DatasetTimeSeriesSerializer(serializers.Serializer):

def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)

choices = self._get_choices()
self.fields["attribute"].choices = choices
self.fields["end_attribute"].choices = choices

@staticmethod
def _get_choices():

attributes = Attribute.objects.all()

return [(None, "-----")] + [
(_a.pk, _a.attribute) for _a in attributes if _a.attribute_type in ["xsd:dateTime", "xsd:date"]
]
giohappy marked this conversation as resolved.
Show resolved Hide resolved

has_time = serializers.BooleanField(default=False)
attribute = serializers.ChoiceField(choices=[], required=False)
end_attribute = serializers.ChoiceField(choices=[], required=False)
presentation = serializers.ChoiceField(
required=False,
choices=[
("LIST", "List of all the distinct time values"),
("DISCRETE_INTERVAL", "Intervals defined by the resolution"),
(
"CONTINUOUS_INTERVAL",
"Continuous Intervals for data that is frequently updated, resolution describes the frequency of updates",
),
],
)
precision_value = serializers.IntegerField(required=False)
precision_step = serializers.ChoiceField(
required=False,
choices=[("years",) * 2, ("months",) * 2, ("days",) * 2, ("hours",) * 2, ("minutes",) * 2, ("seconds",) * 2],
)
105 changes: 105 additions & 0 deletions geonode/layers/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,25 @@
from geonode.resource.utils import update_resource
from geonode.resource.manager import resource_manager
from rest_framework.exceptions import NotFound
from django.shortcuts import get_object_or_404
from django.http import JsonResponse

from geonode.storage.manager import StorageManager

from .serializers import (
DatasetSerializer,
DatasetListSerializer,
DatasetMetadataSerializer,
DatasetTimeSeriesSerializer,
)
from .permissions import DatasetPermissionsFilter

from geonode import geoserver
from geonode.utils import check_ogc_backend

if check_ogc_backend(geoserver.BACKEND_PACKAGE):
from geonode.geoserver.helpers import get_time_info

import logging

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -80,6 +89,8 @@ class DatasetViewSet(ApiPresetsInitializer, DynamicModelViewSet, AdvertisedListM
def get_serializer_class(self):
if self.action == "list":
return DatasetListSerializer
if self.action == "timeseries_info":
return DatasetTimeSeriesSerializer
return DatasetSerializer

def partial_update(self, request, *args, **kwargs):
Expand Down Expand Up @@ -187,3 +198,97 @@ def maps(self, request, pk=None, *args, **kwargs):
dataset = self.get_object()
resources = dataset.maps
return Response(SimpleMapSerializer(many=True).to_representation(resources))

@action(
detail=True,
url_path="timeseries",
url_name="timeseries",
methods=["get", "put"],
permission_classes=[IsAuthenticated],
)
def timeseries_info(self, request, pk, *args, **kwards):
"""
Endpoint for timeseries information

url = "http://localhost:8080/api/v2/datasets/{dataset_id}/timeseries"

cURL examples:
GET method
curl -X GET http://localhost:8000/api/v2/datasets/1/timeseries -u <username>:<password>

PUT method
curl -X PUT http://localhost:8000/api/v2/datasets/1/timeseries -u <username>:<password>
-H "Content-Type: application/json" -d '{"has_time": true, "attribute": 4, "end_attribute": 5,
"presentation": "DISCRETE_INTERVAL", "precision_value": 2, "precision_step": "months"}'
"""

layer = get_object_or_404(Dataset, id=pk)

if layer.supports_time is False:
return JsonResponse({"message": "The time dimension is not supported for raster data."}, status=200)

if request.method == "GET":

serializer = DatasetTimeSeriesSerializer
time_info = get_time_info(layer)
serialized_time_info = serializer(get_time_info(layer)).data

if layer.has_time is True and time_info is not None:
serialized_time_info["has_time"] = layer.has_time
return JsonResponse(serialized_time_info, status=200)
else:
return JsonResponse({"message": "No time information available."}, status=404)

if request.method == "PUT":

serializer = DatasetTimeSeriesSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serialized_time_info = serializer.validated_data

if serialized_time_info.get("has_time") is True:

start_attr = (
layer.attributes.get(pk=serialized_time_info.get("attribute")).attribute
if serialized_time_info.get("attribute")
else None
)
end_attr = (
layer.attributes.get(pk=serialized_time_info.get("end_attribute")).attribute
if serialized_time_info.get("end_attribute")
else None
)

# Save the has_time value to the database
layer.has_time = True
layer.save()

resource_manager.exec(
"set_time_info",
None,
instance=layer,
time_info={
"attribute": start_attr,
"end_attribute": end_attr,
"presentation": serialized_time_info.get("presentation", None),
"precision_value": serialized_time_info.get("precision_value", None),
"precision_step": serialized_time_info.get("precision_step", None),
"enabled": serialized_time_info.get("has_time", False),
},
)

resource_manager.update(
layer.uuid,
instance=layer,
notify=True,
)
return JsonResponse({"message": "the time information data was updated successfully"}, status=200)
else:
# Save the has_time value to the database
layer.has_time = False
layer.save()

return JsonResponse(
{
"message": "The time information was not updated since the time dimension is disabled for this layer"
}
)
6 changes: 6 additions & 0 deletions geonode/layers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ def is_vector(self):
def is_raster(self):
return self.subtype == "raster"

@property
def supports_time(self):
if self.is_vector():
giohappy marked this conversation as resolved.
Show resolved Hide resolved
return True
return False

@property
def display_type(self):
if self.subtype in ["vector", "vector_time"]:
Expand Down
27 changes: 4 additions & 23 deletions geonode/layers/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
from geonode.geoserver.helpers import ogc_server_settings

if check_ogc_backend(geoserver.BACKEND_PACKAGE):
from geonode.geoserver.helpers import gs_catalog
from geonode.geoserver.helpers import gs_catalog, get_time_info

CONTEXT_LOG_FILE = ogc_server_settings.LOG_FILE

Expand Down Expand Up @@ -332,28 +332,9 @@ def dataset_metadata(
prefix="category_choice_field", initial=topic_category.id if topic_category else None
)

gs_layer = gs_catalog.get_layer(name=layer.name)
initial = {}
if gs_layer is not None and layer.has_time:
gs_time_info = gs_layer.resource.metadata.get("time")
if gs_time_info.enabled:
_attr = layer.attributes.filter(attribute=gs_time_info.attribute).first()
initial["attribute"] = _attr.pk if _attr else None
if gs_time_info.end_attribute is not None:
end_attr = layer.attributes.filter(attribute=gs_time_info.end_attribute).first()
initial["end_attribute"] = end_attr.pk if end_attr else None
initial["presentation"] = gs_time_info.presentation
lookup_value = sorted(list(gs_time_info._lookup), key=lambda x: x[1], reverse=True)
if gs_time_info.resolution is not None:
res = gs_time_info.resolution // 1000
for el in lookup_value:
if res % el[1] == 0:
initial["precision_value"] = res // el[1]
initial["precision_step"] = el[0]
break
else:
initial["precision_value"] = gs_time_info.resolution
initial["precision_step"] = "seconds"
if layer.supports_time and layer.has_time:
initial = get_time_info(layer)

timeseries_form = DatasetTimeSerieForm(instance=layer, prefix="timeseries", initial=initial)

Expand Down Expand Up @@ -465,7 +446,7 @@ def dataset_metadata(
layer.has_time = dataset_form.cleaned_data.get("has_time", layer.has_time)

if (
layer.is_vector()
layer.supports_time
and timeseries_form.cleaned_data
and ("has_time" in dataset_form.changed_data or timeseries_form.changed_data)
):
Expand Down
20 changes: 20 additions & 0 deletions geonode/upload/migrations/0050_alter_uploadsizelimit_max_size.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2.16 on 2024-12-06 08:35

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("upload", "0049_move_data_from_importer_to_upload"),
]

operations = [
migrations.AlterField(
model_name="uploadsizelimit",
name="max_size",
field=models.PositiveBigIntegerField(
default=104857600, help_text="The maximum file size allowed for upload (bytes)."
),
),
]
Loading