Skip to content

Commit 2d2366e

Browse files
gjmooneymartinRenoubrichet
authored
Tif layer notebook API (#139)
* Some tif fixes * Add notebook api for symbology colors * Add test and remove max item from schema * Apply suggestions from code review Co-authored-by: Nicolas Brichet <[email protected]> --------- Co-authored-by: martinRenou <[email protected]> Co-authored-by: Nicolas Brichet <[email protected]>
1 parent c023a5c commit 2d2366e

File tree

8 files changed

+192
-44
lines changed

8 files changed

+192
-44
lines changed

packages/base/src/commands.ts

+2-11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '@jupytergis/schema';
1212
import { JupyterFrontEnd } from '@jupyterlab/application';
1313
import { WidgetTracker, showErrorMessage } from '@jupyterlab/apputils';
14+
import { ICompletionProviderManager } from '@jupyterlab/completer';
1415
import { IStateDB } from '@jupyterlab/statedb';
1516
import { ITranslator } from '@jupyterlab/translation';
1617
import { CommandIDs, icons } from './constants';
@@ -19,7 +20,6 @@ import { LayerBrowserWidget } from './dialogs/layerBrowserDialog';
1920
import { SymbologyWidget } from './dialogs/symbologyDialog';
2021
import { TerrainDialogWidget } from './dialogs/terrainDialog';
2122
import { JupyterGISWidget } from './widget';
22-
import { ICompletionProviderManager } from '@jupyterlab/completer';
2323

2424
interface ICreateEntry {
2525
tracker: WidgetTracker<JupyterGISWidget>;
@@ -300,16 +300,7 @@ export function addCommands(
300300
createSource: true,
301301
sourceData: {
302302
name: 'Custom GeoTiff Source',
303-
urls: [
304-
{
305-
url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/B04.tif',
306-
max: 10000
307-
},
308-
{
309-
url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/B08.tif',
310-
max: 10000
311-
}
312-
]
303+
urls: ['']
313304
},
314305
layerData: { name: 'Custom GeoTiff Layer' },
315306
sourceType: 'GeoTiffSource',

packages/base/src/dialogs/components/symbology/SingleBandPseudoColor.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const SingleBandPseudoColor = ({
8383

8484
const setInitialFunction = () => {
8585
if (!layer.parameters?.color) {
86+
setSelectedFunction('linear');
8687
return;
8788
}
8889

@@ -131,6 +132,8 @@ const SingleBandPseudoColor = ({
131132
const tifDataset = result.datasets[0];
132133
tifData = await Gdal.gdalinfo(tifDataset, ['-stats']);
133134
Gdal.close(tifDataset);
135+
136+
state.save(layerId, JSON.stringify(tifData));
134137
}
135138

136139
tifData['bands'].forEach((bandData: TifBandData) => {
@@ -147,9 +150,6 @@ const SingleBandPseudoColor = ({
147150
});
148151
});
149152
setBandRows(bandsArr);
150-
151-
console.log('tifData', tifData);
152-
console.log('bandsArr', bandsArr);
153153
};
154154

155155
const buildColorInfo = () => {
@@ -269,7 +269,7 @@ const SingleBandPseudoColor = ({
269269
});
270270

271271
// fallback value
272-
colorExpr.push([0, 0, 0]);
272+
colorExpr.push([0, 0, 0, 1.0]);
273273
break;
274274
}
275275
case 'exact': {
@@ -289,7 +289,7 @@ const SingleBandPseudoColor = ({
289289
});
290290

291291
// fallback value
292-
colorExpr.push([0, 0, 0]);
292+
colorExpr.push([0, 0, 0, 1.0]);
293293
break;
294294
}
295295
}

packages/base/src/mainview/mainView.tsx

+18-12
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import {
1616
IRasterDemSource,
1717
IRasterLayer,
1818
IRasterSource,
19+
IShapefileSource,
1920
IVectorLayer,
2021
IVectorTileLayer,
2122
IVectorTileSource,
22-
IShapefileSource,
2323
IWebGlLayer,
2424
JupyterGISModel
2525
} from '@jupytergis/schema';
@@ -28,6 +28,7 @@ import { User } from '@jupyterlab/services';
2828
import { JSONValue, UUID } from '@lumino/coreutils';
2929
import { Map as OlMap, View } from 'ol';
3030
import { FeatureLike } from 'ol/Feature';
31+
import { ScaleLine } from 'ol/control';
3132
import { GeoJSON, MVT } from 'ol/format';
3233
import DragAndDrop from 'ol/interaction/DragAndDrop';
3334
import {
@@ -50,14 +51,13 @@ import {
5051
} from 'ol/source';
5152
import Static from 'ol/source/ImageStatic';
5253
import { Circle, Fill, Stroke, Style } from 'ol/style';
53-
import { ScaleLine } from 'ol/control';
5454
//@ts-expect-error no types for ol-pmtiles
5555
import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
5656
import * as React from 'react';
57+
import shp from 'shpjs';
5758
import { isLightTheme } from '../tools';
5859
import { MainViewModel } from './mainviewmodel';
5960
import { Spinner } from './spinner';
60-
import shp from 'shpjs';
6161

6262
interface IProps {
6363
viewModel: MainViewModel;
@@ -598,13 +598,17 @@ export class MainView extends React.Component<IProps, IStates> {
598598
case 'WebGlLayer': {
599599
layerParameters = layer.parameters as IWebGlLayer;
600600

601-
newLayer = new WebGlTileLayer({
601+
// This is to handle python sending a None for the color
602+
const layerOptions: any = {
602603
opacity: layerParameters.opacity,
603-
source: this._sources[layerParameters.source],
604-
style: {
605-
color: layerParameters.color
606-
}
607-
});
604+
source: this._sources[layerParameters.source]
605+
};
606+
607+
if (layerParameters.color) {
608+
layerOptions['style'] = { color: layerParameters.color };
609+
}
610+
611+
newLayer = new WebGlTileLayer(layerOptions);
608612

609613
break;
610614
}
@@ -849,9 +853,11 @@ export class MainView extends React.Component<IProps, IStates> {
849853
case 'WebGlLayer': {
850854
mapLayer.setOpacity(layer.parameters?.opacity);
851855

852-
(mapLayer as WebGlTileLayer).setStyle({
853-
color: layer?.parameters?.color
854-
});
856+
if (layer?.parameters?.color) {
857+
(mapLayer as WebGlTileLayer).setStyle({
858+
color: layer.parameters.color
859+
});
860+
}
855861
break;
856862
}
857863
}

packages/schema/src/schema/geoTiffSource.json

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
}
2323
},
2424
"minItems": 1,
25-
"default": [],
2625
"description": "URLs"
2726
},
2827
"normalize": {

packages/schema/src/schema/webGlLayer.json

+14-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"color": {
2121
"oneOf": [
2222
{ "type": "string" },
23+
{ "type": "number" },
2324
{
2425
"type": "array",
2526
"items": {
@@ -28,14 +29,24 @@
2829
{ "type": "number" },
2930
{
3031
"type": "array",
31-
"items": { "type": "string" }
32+
"items": {
33+
"anyOf": [
34+
{ "type": "number" },
35+
{ "type": "string" },
36+
{
37+
"type": "array",
38+
"items": {
39+
"anyOf": [{ "type": "number" }, { "type": "string" }]
40+
}
41+
}
42+
]
43+
}
3244
}
3345
]
3446
}
3547
}
3648
],
37-
"description": "The color of the the object",
38-
"default": "#FF0000"
49+
"description": "The color of the the object"
3950
}
4051
}
4152
}

python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py

+107-11
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,29 @@
44
import logging
55
from pathlib import Path
66
from typing import Any, Dict, List, Literal, Optional, Union
7+
from uuid import uuid4
78

89
from pycrdt import Array, Doc, Map
910
from pydantic import BaseModel
1011
from ypywidgets.comm import CommWidget
1112

12-
from uuid import uuid4
13-
14-
from .utils import normalize_path, get_source_layer_names
15-
1613
from .objects import (
17-
LayerType,
18-
SourceType,
14+
IGeoJSONSource,
1915
IHillshadeLayer,
2016
IImageLayer,
17+
IImageSource,
2118
IRasterLayer,
2219
IRasterSource,
23-
IVectorTileSource,
2420
IVectorLayer,
2521
IVectorTileLayer,
26-
IGeoJSONSource,
27-
IImageSource,
22+
IVectorTileSource,
2823
IVideoSource,
29-
IWebGlLayer
24+
IGeoTiffSource,
25+
IWebGlLayer,
26+
LayerType,
27+
SourceType,
3028
)
29+
from .utils import get_source_layer_names, normalize_path
3130

3231
logger = logging.getLogger(__file__)
3332

@@ -360,7 +359,102 @@ def add_video_layer(
360359
}
361360

362361
return self._add_layer(OBJECT_FACTORY.create_layer(layer, self))
362+
363+
def add_tiff_layer(
364+
self,
365+
url: str,
366+
min: int = None,
367+
max: int = None,
368+
name: str = "Tiff Layer",
369+
normalize: bool = True,
370+
wrapX: bool = False,
371+
attribution: str = "",
372+
opacity: float = 1.0,
373+
color_expr = None
374+
):
375+
"""
376+
Add a tiff layer
377+
378+
:param str url: URL of the tif
379+
:param int min: Minimum pixel value to be displayed, defaults to letting the map display set the value
380+
:param int max: Maximum pixel value to be displayed, defaults to letting the map display set the value
381+
:param str name: The name that will be used for the object in the document, defaults to "Tiff Layer"
382+
:param bool normalize: Select whether to normalize values between 0..1, if false than min/max have no effect, defaults to True
383+
:param bool wrapX: Render tiles beyond the tile grid extent, defaults to False
384+
:param float opacity: The opacity, between 0 and 1, defaults to 1.0
385+
:param _type_ color_expr: The style expression used to style the layer, defaults to None
386+
"""
387+
388+
source = {
389+
"type": SourceType.GeoTiffSource,
390+
"name": f"{name} Source",
391+
"parameters": {
392+
"urls": [{
393+
"url": url,
394+
"min": min,
395+
"max": max
396+
}],
397+
"normalize": normalize,
398+
"wrapX": wrapX,
399+
},
400+
}
401+
source_id = self._add_source(OBJECT_FACTORY.create_source(source, self))
402+
403+
layer = {
404+
"type": LayerType.WebGlLayer,
405+
"name": name,
406+
"visible": True,
407+
"parameters": {"source": source_id, "opacity": opacity, "color": color_expr},
408+
}
363409

410+
return self._add_layer(OBJECT_FACTORY.create_layer(layer, self))
411+
412+
def create_color_expr(self, color_stops: Dict, band: float = 1.0, interpolation_type: str = 'linear', ):
413+
"""
414+
Create a color expression used to style the layer
415+
416+
:param Dict color_stops: Dictionary of stop values to [r, g, b, a] colors
417+
:param float band: The band to be colored, defaults to 1.0
418+
:param str interpolation_type: The interpolation function. Can be linear, discrete, or exact, defaults to 'linear'
419+
"""
420+
421+
if interpolation_type not in ["linear", "discrete", "exact"]:
422+
raise ValueError("Interpolation type must be one of linear, discrete, or exact")
423+
424+
color = []
425+
if interpolation_type == 'linear':
426+
color = ['interpolate',['linear']]
427+
color.append(['band', band])
428+
# Transparency for nodata
429+
color.append(0.0)
430+
color.append([0.0, 0.0, 0.0, 0.0])
431+
432+
for value, colorVal in color_stops.items():
433+
color.append(value)
434+
color.append(colorVal)
435+
436+
return color
437+
438+
if interpolation_type == 'discrete':
439+
operator = '<='
440+
441+
if interpolation_type == 'exact':
442+
operator = '=='
443+
444+
color = ['case']
445+
# Transparency for nodata
446+
color.append(["==", ["band", band], 0.0])
447+
color.append([0.0, 0.0, 0.0, 0.0])
448+
449+
for value, colorVal in color_stops.items():
450+
color.append([operator, ["band", band], value])
451+
color.append(colorVal)
452+
453+
# Fallback color
454+
color.append([0.0, 0.0, 0.0, 1.0])
455+
456+
return color
457+
364458
def add_filter(self, layer_id: str, logical_op:str, feature:str, operator:str, value:Union[str, number, float]):
365459
"""
366460
Add a filter to a layer
@@ -529,7 +623,8 @@ class Config:
529623
IVectorTileSource,
530624
IGeoJSONSource,
531625
IImageSource,
532-
IVideoSource
626+
IVideoSource,
627+
IGeoTiffSource
533628
]
534629
_parent = Optional[GISDocument]
535630

@@ -614,3 +709,4 @@ def create_source(
614709
OBJECT_FACTORY.register_factory(SourceType.GeoJSONSource, IGeoJSONSource)
615710
OBJECT_FACTORY.register_factory(SourceType.ImageSource, IImageSource)
616711
OBJECT_FACTORY.register_factory(SourceType.VideoSource, IVideoSource)
712+
OBJECT_FACTORY.register_factory(SourceType.GeoTiffSource, IGeoTiffSource)

python/jupytergis_lab/jupytergis_lab/notebook/objects/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
from ._schema.geojsonsource import IGeoJSONSource # noqa
1313
from ._schema.videoSource import IVideoSource # noqa
1414
from ._schema.imageSource import IImageSource # noqa
15+
from ._schema.geoTiffSource import IGeoTiffSource # noqa

0 commit comments

Comments
 (0)