From 58be11c0c51c96926d38020c4ff81d013210001f Mon Sep 17 00:00:00 2001 From: Adam Cox Date: Mon, 16 Mar 2026 23:47:21 -0500 Subject: [PATCH 1/5] drive mosaic layers from unified schema --- ohmg/api/schemas.py | 29 ++++++++++- .../src/components/Viewer.svelte | 51 +++++++++++-------- ohmg/places/views.py | 4 +- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/ohmg/api/schemas.py b/ohmg/api/schemas.py index 84d7083e..e3441ec2 100644 --- a/ohmg/api/schemas.py +++ b/ohmg/api/schemas.py @@ -1,7 +1,7 @@ import json import logging from datetime import datetime -from typing import Any, List, Literal, Optional +from typing import TYPE_CHECKING, Any, List, Literal, Optional import humanize from avatar.templatetags.avatar_tags import avatar_url @@ -23,6 +23,9 @@ SessionLock, ) +if TYPE_CHECKING: + from ohmg.core.models import LayerSet + logger = logging.getLogger(__name__) @@ -504,6 +507,30 @@ def resolve_name(obj): return str(obj.category) +class LayerSetDisplaySchema(Schema): + map_id: str + category: str + extent: Optional[tuple] + layers_tilejson: list[dict] + multimask_geojson: Optional[dict] + + @staticmethod + def resolve_category(obj): + return str(obj.category) + + @staticmethod + def resolve_layers_tilejson(obj: "LayerSet"): + layers_tilejson = [] + if obj.mosaic_geotiff: + layers_tilejson.append(obj.tilejson) + else: + for layer in natsorted(obj.get_layers(), key=lambda k: k.title): + tilejson = layer.tilejson + tilejson["mask"] = json.loads(layer.mask.geojson) + layers_tilejson.append(tilejson) + return layers_tilejson + + class PlaceSchema(Schema): """very lightweight serialization of a Place with its Maps""" diff --git a/ohmg/frontend/svelte_components/src/components/Viewer.svelte b/ohmg/frontend/svelte_components/src/components/Viewer.svelte index 17daaa11..eef07766 100644 --- a/ohmg/frontend/svelte_components/src/components/Viewer.svelte +++ b/ohmg/frontend/svelte_components/src/components/Viewer.svelte @@ -27,20 +27,27 @@ import XYZ from 'ol/source/XYZ'; import VectorSource from 'ol/source/Vector'; + import TileJSON from 'ol/source/TileJSON'; + + import GeoJSON from 'ol/format/GeoJSON'; import TileLayer from 'ol/layer/Tile'; import VectorLayer from 'ol/layer/Vector'; + import LayerGroup from 'ol/layer/Group'; - import { makeTitilerXYZUrl, makeLayerGroupFromLayerSet } from '../lib/utils'; import { MapViewer } from '../lib/viewers'; import Modal, { getModal } from './modals/BaseModal.svelte'; import Link from './common/Link.svelte'; import MapboxLogoLink from './common/MapboxLogoLink.svelte'; + import Crop from 'ol-ext/filter/Crop'; + export let CONTEXT; export let PLACE; export let MAPS; + console.log(MAPS) + let showPanel = true; let volumeIds = []; @@ -90,29 +97,33 @@ let mainGroup; let mosaicType; - if (vol.main_layerset.layers.length > 0 && vol.main_layerset.extent) { + + if (vol.main_layerset.layers_tilejson.length > 0) { const mainExtent = transformExtent(vol.main_layerset.extent, 'EPSG:4326', 'EPSG:3857'); extend(homeExtent, mainExtent); - if (vol.main_layerset.mosaic_cog_url) { - mainGroup = new TileLayer({ - source: new XYZ({ - transition: 0, - url: makeTitilerXYZUrl({ - host: CONTEXT.titiler_host, - url: vol.main_layerset.mosaic_cog_url, - }), + + mainGroup = new LayerGroup(); + + vol.main_layerset.layers_tilejson.forEach((tilejson) => { + const lyr = new TileLayer({ + source: new TileJSON({ + tileJSON: tilejson, + tileSize: 512, }), - extent: transformExtent(vol.main_layerset.multimask_extent, 'EPSG:4326', 'EPSG:3857'), + extent: transformExtent(tilejson.bounds, 'EPSG:4326', 'EPSG:3857'), }); - mosaicType = 'gt'; - } else { - mainGroup = makeLayerGroupFromLayerSet({ - layerSet: vol.main_layerset, - zIndex: 400 + n, - titilerHost: CONTEXT.titiler_host, - applyMultiMask: true, - }); - } + if (tilejson.mask) { + const feature = new GeoJSON().readFeature(tilejson.mask); + feature.getGeometry().transform('EPSG:4326', 'EPSG:3857'); + const crop = new Crop({ + feature: feature, + wrapX: true, + inner: false, + }); + lyr.addFilter(crop); + } + mainGroup.getLayers().push(lyr); + }); } let opacity = 0; diff --git a/ohmg/places/views.py b/ohmg/places/views.py index b7585ecf..ef59daf1 100644 --- a/ohmg/places/views.py +++ b/ohmg/places/views.py @@ -4,7 +4,7 @@ from natsort import natsorted from ohmg.api.schemas import ( - LayerSetSchema, + LayerSetDisplaySchema, MapFullSchema, PlaceFullSchema, ) @@ -45,7 +45,7 @@ def get(self, request, place): map_json = MapFullSchema.from_orm(map).dict() ls = map.get_layerset("main-content") if ls: - map_json["main_layerset"] = LayerSetSchema.from_orm(ls).dict() + map_json["main_layerset"] = LayerSetDisplaySchema.from_orm(ls).dict() maps.append(map_json) maps_sorted = natsorted(maps, key=lambda x: x["title"], reverse=True) From 3b63fd1cd0ec3796ee0080ca8174b7243913e655 Mon Sep 17 00:00:00 2001 From: Adam Cox Date: Tue, 17 Mar 2026 14:48:06 +0000 Subject: [PATCH 2/5] handle no mask on layer --- ohmg/api/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ohmg/api/schemas.py b/ohmg/api/schemas.py index e3441ec2..c6927054 100644 --- a/ohmg/api/schemas.py +++ b/ohmg/api/schemas.py @@ -526,7 +526,7 @@ def resolve_layers_tilejson(obj: "LayerSet"): else: for layer in natsorted(obj.get_layers(), key=lambda k: k.title): tilejson = layer.tilejson - tilejson["mask"] = json.loads(layer.mask.geojson) + tilejson["mask"] = json.loads(layer.mask.geojson) if layer.mask else None layers_tilejson.append(tilejson) return layers_tilejson From ebb810f717f16f2f5799f0206a0f0dc9a123327d Mon Sep 17 00:00:00 2001 From: Adam Cox Date: Tue, 17 Mar 2026 14:50:17 +0000 Subject: [PATCH 3/5] use uv during frontend deploy --- scripts/deploy_frontend.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/deploy_frontend.sh b/scripts/deploy_frontend.sh index 84e21148..20e981a8 100644 --- a/scripts/deploy_frontend.sh +++ b/scripts/deploy_frontend.sh @@ -16,10 +16,13 @@ pnpm run build cd $CURRENT_DIR echo "getting static plugin assets" -$VIRTUAL_ENV/bin/python $PROJECT_ROOT/manage.py get-plugins +uv run $PROJECT_ROOT/manage.py get-plugins echo "running collectstatic" -$VIRTUAL_ENV/bin/python $PROJECT_ROOT/manage.py collectstatic --noinput +uv run $PROJECT_ROOT/manage.py collectstatic --noinput echo "update build number" -$VIRTUAL_ENV/bin/python $PROJECT_ROOT/manage.py update_build +uv run $PROJECT_ROOT/manage.py update_build + +echo "touch wsgi.py to reset uwsgi" +touch ohmg/conf/wsgi.py From 32b934dde430bf92b6e43050a0ba010b1c17f5a7 Mon Sep 17 00:00:00 2001 From: Adam Cox Date: Tue, 17 Mar 2026 10:09:56 -0500 Subject: [PATCH 4/5] use ligher Map schema for viewer --- .../svelte_components/src/components/Viewer.svelte | 11 +++++++---- ohmg/places/views.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ohmg/frontend/svelte_components/src/components/Viewer.svelte b/ohmg/frontend/svelte_components/src/components/Viewer.svelte index eef07766..25b726f5 100644 --- a/ohmg/frontend/svelte_components/src/components/Viewer.svelte +++ b/ohmg/frontend/svelte_components/src/components/Viewer.svelte @@ -139,9 +139,12 @@ const volumeObj = { id: vol.identifier, - summaryUrl: vol.urls.summary, + summaryUrl: `/map/${vol.identifier}`, displayName: vol.volume_number ? `${vol.year} vol. ${vol.volume_number}` : vol.year, - progress: vol.progress, + unprepared_ct: vol.unprepared_ct, + prepared_ct: vol.prepared_ct, + layer_ct: vol.layer_ct, + completion_pct: vol.completion_pct, mainLayer: mainGroup, mainLayerO: opacity, mosaicType: mosaicType, @@ -357,7 +360,7 @@ } function getCompletedStr(id) { - return `${volumeLookup[id].progress.georef_ct}/${volumeLookup[id].progress.unprep_ct + volumeLookup[id].progress.prep_ct + volumeLookup[id].progress.georef_ct}`; + return `${volumeLookup[id].layer_ct}/${volumeLookup[id].unprepared_ct + volumeLookup[id].prepared_ct + volumeLookup[id].layer_ct}`; } @@ -479,7 +482,7 @@
- {volumeLookup[id].progress.percent}% ({getCompletedStr(id)}) + {volumeLookup[id].completion_pct}% ({getCompletedStr(id)}) {#if volumeLookup[id].mosaicType}