Skip to content

Commit

Permalink
[WIP] Retrieve map layers from dataset detail endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jjnesbitt committed Nov 15, 2023
1 parent c96ff90 commit e28a322
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 84 deletions.
63 changes: 46 additions & 17 deletions uvdat/core/models/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,52 @@ def get_network_gcc(self, exclude_nodes):
return get_dataset_network_gcc(self, exclude_nodes)

def get_map_layers(self):
"""Returns a queryset of either RasterMapLayer, or VectorMapLayer."""
from uvdat.core.models import RasterMapLayer, VectorMapLayer

ret = []
for vector_map_layer in VectorMapLayer.objects.filter(file_item__dataset=self):
ret.append(
{
'id': vector_map_layer.id,
'index': vector_map_layer.index,
'type': 'vector',
}
)
for raster_map_layer in RasterMapLayer.objects.filter(file_item__dataset=self):
ret.append(
{
'id': raster_map_layer.id,
'index': raster_map_layer.index,
'type': 'raster',
}
if self.dataset_type == self.DatasetType.RASTER:
return RasterMapLayer.objects.filter(file_item__dataset=self)
if self.dataset_type == self.DatasetType.VECTOR:
return VectorMapLayer.objects.filter(file_item__dataset=self)

raise NotImplementedError(f"Dataset Type {self.dataset_type}")

def get_map_layer_tile_extents(self):
"""
Return the extents of all vector map layers of this dataset.
Returns `None` if the dataset is not a vector dataset.
"""
if self.dataset_type != self.DatasetType.VECTOR:
return None

from uvdat.core.models import VectorMapLayer, VectorTile

# Retrieve all layers
layer_ids = VectorMapLayer.objects.filter(file_item__dataset=self).values_list(
'id', flat=True
)

# Return x/y extents by layer id and z depth
vals = (
VectorTile.objects.filter(map_layer_id__in=layer_ids)
.values('map_layer_id', 'z')
.annotate(
min_x=models.Min('x'),
min_y=models.Min('y'),
max_x=models.Max('x'),
max_y=models.Max('y'),
)
return ret
.order_by('map_layer_id')
)

# Deconstruct query into response format
layers = {}
for entry in vals:
map_layer_id = entry.pop('map_layer_id')
if map_layer_id not in layers:
layers[map_layer_id] = {}

layers[map_layer_id][entry.pop('z')] = entry

return layers
17 changes: 17 additions & 0 deletions uvdat/core/rest/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.http import HttpResponse
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from uvdat.core.models import Dataset
Expand All @@ -19,6 +20,22 @@ def get_queryset(self):
else:
return Dataset.objects.all()

def retrieve(self, request, *args, **kwargs):
dataset: Dataset = self.get_object()

# Get map layers
dataset.map_layers = list(dataset.get_map_layers().select_related('file_item__dataset'))

# Set vector tile extents if vector dataset
if dataset.dataset_type == Dataset.DatasetType.VECTOR:
extents = dataset.get_map_layer_tile_extents()
for layer in dataset.map_layers:
layer.tile_extents = extents.pop(layer.id)

# Serialize and return
serializer = uvdat_serializers.DatasetDetailSerializer(dataset)
return Response(serializer.data, status=200)

@action(detail=True, methods=['get'])
def convert(self, request, **kwargs):
dataset = self.get_object()
Expand Down
15 changes: 12 additions & 3 deletions uvdat/core/rest/map_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@

from uvdat.core.models import RasterMapLayer, VectorMapLayer
from uvdat.core.models.map_layers import VectorTile
from uvdat.core.rest.serializers import RasterMapLayerSerializer, VectorMapLayerSerializer
from uvdat.core.rest.serializers import (
RasterMapLayerSerializer,
VectorMapLayerDetailSerializer,
VectorMapLayerSerializer,
)


class RasterMapLayerViewSet(ModelViewSet, LargeImageFileDetailMixin):
queryset = RasterMapLayer.objects.all()
queryset = RasterMapLayer.objects.select_related('file_item__dataset').all()
serializer_class = RasterMapLayerSerializer
FILE_FIELD_NAME = 'cloud_optimized_geotiff'

Expand All @@ -29,9 +33,14 @@ def get_raster_data(self, request, resolution: str = '1', **kwargs):


class VectorMapLayerViewSet(ModelViewSet):
queryset = VectorMapLayer.objects.all()
queryset = VectorMapLayer.objects.select_related('file_item__dataset').all()
serializer_class = VectorMapLayerSerializer

def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = VectorMapLayerDetailSerializer(instance)
return Response(serializer.data)

@action(
detail=True,
methods=['get'],
Expand Down
77 changes: 45 additions & 32 deletions uvdat/core/rest/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,30 @@ class Meta:


class DatasetSerializer(serializers.ModelSerializer):
map_layers = serializers.SerializerMethodField('get_map_layers')
class Meta:
model = Dataset
fields = '__all__'

def get_map_layers(self, obj):
return obj.get_map_layers()

class DatasetDetailSerializer(serializers.ModelSerializer):
map_layers = serializers.SerializerMethodField()

def get_map_layers(self, obj: Dataset):
map_layers = getattr(obj, 'map_layers', None)
if map_layers is None:
raise Exception(f'"map_layer" attribute not found on Dataset {obj.id}')

# Set serializer class
sub_serializer_cls = None
if obj.dataset_type == Dataset.DatasetType.VECTOR:
sub_serializer_cls = DatasetVectorMapLayerSerializer
if obj.dataset_type == Dataset.DatasetType.RASTER:
sub_serializer_cls = RasterMapLayerSerializer
if sub_serializer_cls is None:
raise NotImplementedError(f'Dataset Type {obj.dataset_type}')

serializer = sub_serializer_cls(map_layers, many=True)
return serializer.data

class Meta:
model = Dataset
Expand All @@ -54,66 +74,59 @@ class Meta:
fields = '__all__'


class RasterMapLayerSerializer(serializers.ModelSerializer):
class AbstractMapLayerSerializer(serializers.Serializer):
name = serializers.SerializerMethodField('get_name')
type = serializers.SerializerMethodField('get_type')
dataset_id = serializers.SerializerMethodField('get_dataset_id')
file_item = serializers.SerializerMethodField('get_file_item')

def get_name(self, obj):
def get_name(self, obj: VectorMapLayer | RasterMapLayer):
if obj.file_item is None:
return None
return obj.file_item.name

def get_type(self, obj):
def get_type(self, obj: VectorMapLayer | RasterMapLayer):
if isinstance(obj, VectorMapLayer):
return 'vector'
return 'raster'

def get_dataset_id(self, obj):
def get_dataset_id(self, obj: VectorMapLayer | RasterMapLayer):
if obj.file_item and obj.file_item.dataset:
return obj.file_item.dataset.id
return None

def get_file_item(self, obj):
def get_file_item(self, obj: VectorMapLayer | RasterMapLayer):
if obj.file_item is None:
return None
return {
'id': obj.file_item.id,
'name': obj.file_item.name,
}


class RasterMapLayerSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer):
class Meta:
model = RasterMapLayer
fields = '__all__'


class VectorMapLayerSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField('get_name')
type = serializers.SerializerMethodField('get_type')
dataset_id = serializers.SerializerMethodField('get_dataset_id')
file_item = serializers.SerializerMethodField('get_file_item')
derived_region_id = serializers.SerializerMethodField('get_derived_region_id')
tile_extents = serializers.SerializerMethodField('get_tile_extents')
class DatasetVectorMapLayerSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer):
tile_extents = serializers.JSONField()

def get_name(self, obj):
if obj.file_item is None:
return None
return obj.file_item.name
class Meta:
model = VectorMapLayer
exclude = ['geojson_file']

def get_type(self, obj):
return 'vector'

def get_dataset_id(self, obj):
if obj.file_item and obj.file_item.dataset:
return obj.file_item.dataset.id
return None
class VectorMapLayerSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer):
class Meta:
model = VectorMapLayer
exclude = ['geojson_file']

def get_file_item(self, obj):
if obj.file_item is None:
return None
return {
'id': obj.file_item.id,
'name': obj.file_item.name,
}

class VectorMapLayerDetailSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer):
derived_region_id = serializers.SerializerMethodField('get_derived_region_id')
tile_extents = serializers.SerializerMethodField('get_tile_extents')

def get_derived_region_id(self, obj):
dr = obj.derivedregion_set.first()
Expand Down
21 changes: 16 additions & 5 deletions web/src/components/MainDrawerContents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import {
loadDerivedRegions,
} from "../storeFunctions";
import { Dataset, DerivedRegion } from "@/types";
import { getDataset } from "@/api/rest";
import {
getMapLayerForDataObject,
isMapLayerVisible,
toggleMapLayer,
createOpenLayer,
} from "@/layers";
export default {
Expand Down Expand Up @@ -72,11 +74,20 @@ export default {
) {
currentDataset.value = undefined;
}
// Find related MapLayer at current index
const mapLayer = await getMapLayerForDataObject(
dataset,
dataset.current_layer_index
);
// Ensure layer index is set
dataset.current_layer_index = dataset.current_layer_index || 0;
if (dataset.map_layers === undefined) {
dataset.map_layers = (await getDataset(dataset.id)).map_layers;
}
// Use non null assertions since we know from above that both are set
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mapLayer = dataset.map_layers![dataset.current_layer_index!];
if (mapLayer.openlayer === undefined) {
mapLayer.openlayer = createOpenLayer(mapLayer);
}
toggleMapLayer(mapLayer);
}
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/map/ActiveLayers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default {
getDataObjectForMapLayer(mapLayer);
if (dataObject) {
let ret = dataObject.name;
if (dataObject.map_layers.length > 1) {
if (dataObject.map_layers && dataObject.map_layers.length > 1) {
ret += ` (Layer ${mapLayer.index})`;
}
return ret;
Expand Down
48 changes: 30 additions & 18 deletions web/src/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,32 @@ import CircleStyle from "ol/style/Circle";

const _mapLayerManager = ref<(VectorMapLayer | RasterMapLayer)[]>([]);

export function createOpenLayer(mapLayer: AbstractMapLayer) {
let openLayer: VectorTileLayer | TileLayer<XYZSource>;

if (isVectorMapLayer(mapLayer)) {
openLayer = createVectorOpenLayer(mapLayer);
} else if (isRasterMapLayer(mapLayer)) {
openLayer = createRasterOpenLayer(mapLayer);
styleRasterOpenLayer(mapLayer.openlayer, {});
cacheRasterData(mapLayer.id);
} else {
throw new Error("Unsupported map layer type.");
}

openLayer.setZIndex(selectedMapLayers.value.length);

// Check for existing layer
const existingLayer = _mapLayerManager.value.find(
(l) => l.id === mapLayer.id && l.type === mapLayer.type
);
if (!existingLayer) {
_mapLayerManager.value.push(mapLayer);
}

return openLayer;
}

export async function getOrCreateLayerFromID(
mapLayerId: number | undefined,
mapLayerType: string | undefined
Expand All @@ -43,28 +69,14 @@ export async function getOrCreateLayerFromID(
throw new Error(`Could not get or create openLayer for undefined layer`);
}

let cachedMapLayer = _mapLayerManager.value.find((l) => {
const cachedMapLayer = _mapLayerManager.value.find((l) => {
return l.id === mapLayerId && l.type === mapLayerType;
});
if (cachedMapLayer) return cachedMapLayer;

const mapLayer = await getMapLayer(mapLayerId, mapLayerType);
if (isVectorMapLayer(mapLayer)) {
mapLayer.openlayer = createVectorOpenLayer(mapLayer);
} else if (isRasterMapLayer(mapLayer)) {
mapLayer.openlayer = createRasterOpenLayer(mapLayer);
styleRasterOpenLayer(mapLayer.openlayer, {});
cacheRasterData(mapLayerId);
}
mapLayer.openlayer.setZIndex(selectedMapLayers.value.length);
mapLayer.openlayer = createOpenLayer(mapLayer);

// since this is an async context, check again for existing layer before pushing.
cachedMapLayer = _mapLayerManager.value.find((l) => {
return l.id === mapLayerId && l.type === mapLayerType;
});
if (!cachedMapLayer) {
_mapLayerManager.value.push(mapLayer);
}
return mapLayer;
}

Expand Down Expand Up @@ -292,7 +304,7 @@ export function getDataObjectForMapLayer(
);
if (dataset) {
dataset.current_layer_index =
dataset?.map_layers.find(({ id }) => id === mapLayer.id)?.index || 0;
dataset?.map_layers?.find(({ id }) => id === mapLayer.id)?.index || 0;
}
return dataset;
}
Expand All @@ -308,7 +320,7 @@ export async function getMapLayerForDataObject(
if (dataObject === undefined) {
throw new Error(`Could not get map layer for undefined data object`);
}
const mapLayer = dataObject.map_layers.find(
const mapLayer = dataObject.map_layers?.find(
({ index }) => index === layerIndex
);
if (!mapLayer) {
Expand Down
Loading

0 comments on commit e28a322

Please sign in to comment.