@@ -22,6 +22,7 @@ import {
22
22
IRasterLayer ,
23
23
IRasterSource ,
24
24
IShapefileSource ,
25
+ IStacLayer ,
25
26
IVectorLayer ,
26
27
IVectorTileLayer ,
27
28
IVectorTileSource ,
@@ -51,15 +52,15 @@ import {
51
52
VectorTile as VectorTileLayer ,
52
53
WebGLTile as WebGlTileLayer ,
53
54
} from 'ol/layer' ;
55
+ import LayerGroup from 'ol/layer/Group' ;
54
56
import TileLayer from 'ol/layer/Tile' ;
55
57
import {
56
58
fromLonLat ,
57
- get as getRegisteredProjection ,
59
+ get as getProjection ,
58
60
toLonLat ,
59
61
transformExtent ,
60
62
} from 'ol/proj' ;
61
63
import { register } from 'ol/proj/proj4.js' ;
62
- import { get as getProjection } from 'ol/proj.js' ;
63
64
import RenderFeature from 'ol/render/Feature' ;
64
65
import {
65
66
GeoTIFF as GeoTIFFSource ,
@@ -74,6 +75,7 @@ import { Circle, Fill, Stroke, Style } from 'ol/style';
74
75
import { Rule } from 'ol/style/flat' ;
75
76
//@ts -expect-error no types for ol-pmtiles
76
77
import { PMTilesRasterSource , PMTilesVectorSource } from 'ol-pmtiles' ;
78
+ import StacLayer from 'ol-stac' ;
77
79
import proj4 from 'proj4' ;
78
80
import proj4list from 'proj4-list' ;
79
81
import * as React from 'react' ;
@@ -82,12 +84,21 @@ import AnnotationFloater from '@/src/annotations/components/AnnotationFloater';
82
84
import { CommandIDs } from '@/src/constants' ;
83
85
import { LoadingOverlay } from '@/src/shared/components/loading' ;
84
86
import StatusBar from '@/src/statusbar/StatusBar' ;
85
- import { isLightTheme , loadFile , throttle } from '@/src/tools' ;
87
+ import { debounce , isLightTheme , loadFile , throttle } from '@/src/tools' ;
86
88
import CollaboratorPointers , { ClientPointer } from './CollaboratorPointers' ;
87
89
import { FollowIndicator } from './FollowIndicator' ;
88
90
import TemporalSlider from './TemporalSlider' ;
89
91
import { MainViewModel } from './mainviewmodel' ;
90
92
93
+ type OlLayerTypes =
94
+ | TileLayer
95
+ | VectorLayer
96
+ | VectorTileLayer
97
+ | WebGlTileLayer
98
+ | WebGlTileLayer
99
+ | HeatmapLayer
100
+ | StacLayer
101
+ | ImageLayer < any > ;
91
102
interface IProps {
92
103
viewModel : MainViewModel ;
93
104
}
@@ -205,6 +216,7 @@ export class MainView extends React.Component<IProps, IStates> {
205
216
this . _contextMenu = new ContextMenu ( {
206
217
commands : this . _commands ,
207
218
} ) ;
219
+ this . _updateCenter = debounce ( this . updateCenter , 100 ) ;
208
220
}
209
221
210
222
async componentDidMount ( ) : Promise < void > {
@@ -302,6 +314,8 @@ export class MainView extends React.Component<IProps, IStates> {
302
314
303
315
const view = this . _Map . getView ( ) ;
304
316
317
+ view . on ( 'change:center' , ( ) => this . _updateCenter ( ) ) ;
318
+
305
319
// TODO: Note for the future, will need to update listeners if view changes
306
320
view . on (
307
321
'change:center' ,
@@ -409,6 +423,22 @@ export class MainView extends React.Component<IProps, IStates> {
409
423
}
410
424
}
411
425
426
+ updateCenter = ( ) => {
427
+ const extentIn4326 = this . getViewBbox ( ) ;
428
+ this . _model . updateBboxSignal . emit ( extentIn4326 ) ;
429
+ } ;
430
+
431
+ getViewBbox = ( targetProjection = 'EPSG:4326' ) => {
432
+ const view = this . _Map . getView ( ) ;
433
+ const extent = view . calculateExtent ( this . _Map . getSize ( ) ) ;
434
+
435
+ if ( view . getProjection ( ) . getCode ( ) === targetProjection ) {
436
+ return extent ;
437
+ }
438
+
439
+ return transformExtent ( extent , view . getProjection ( ) , targetProjection ) ;
440
+ } ;
441
+
412
442
createSelectInteraction = ( ) => {
413
443
const pointStyle = new Style ( {
414
444
image : new Circle ( {
@@ -870,24 +900,28 @@ export class MainView extends React.Component<IProps, IStates> {
870
900
private async _buildMapLayer (
871
901
id : string ,
872
902
layer : IJGISLayer ,
873
- ) : Promise < Layer | undefined > {
874
- const sourceId = layer . parameters ?. source ;
875
- const source = this . _model . sharedModel . getLayerSource ( sourceId ) ;
876
- if ( ! source ) {
877
- return ;
878
- }
879
-
903
+ ) : Promise < Layer | StacLayer | undefined > {
880
904
this . setState ( old => ( { ...old , loadingLayer : true } ) ) ;
881
905
this . _loadingLayers . add ( id ) ;
882
906
883
- if ( ! this . _sources [ sourceId ] ) {
884
- await this . addSource ( sourceId , source ) ;
885
- }
907
+ let newMapLayer : OlLayerTypes ;
908
+ let layerParameters : any ;
909
+ let sourceId : string | undefined ;
910
+ let source : IJGISSource | undefined ;
886
911
887
- this . _loadingLayers . add ( id ) ;
888
-
889
- let newMapLayer ;
890
- let layerParameters ;
912
+ if ( layer . type !== 'StacLayer' ) {
913
+ sourceId = layer . parameters ?. source ;
914
+ if ( ! sourceId ) {
915
+ return ;
916
+ }
917
+ source = this . _model . sharedModel . getLayerSource ( sourceId ) ;
918
+ if ( ! source ) {
919
+ return ;
920
+ }
921
+ if ( ! this . _sources [ sourceId ] ) {
922
+ await this . addSource ( sourceId , source ) ;
923
+ }
924
+ }
891
925
892
926
// TODO: OpenLayers provides a bunch of sources for specific tile
893
927
// providers, so maybe set up some way to use those
@@ -977,22 +1011,46 @@ export class MainView extends React.Component<IProps, IStates> {
977
1011
radius : layerParameters . radius ?? 8 ,
978
1012
gradient : layerParameters . color ,
979
1013
} ) ;
1014
+
980
1015
break ;
981
1016
}
982
- }
1017
+ case 'StacLayer' : {
1018
+ layerParameters = layer . parameters as IStacLayer ;
983
1019
984
- await this . _waitForSourceReady ( newMapLayer ) ;
1020
+ newMapLayer = new StacLayer ( {
1021
+ displayPreview : true ,
1022
+ data : layerParameters . data ,
1023
+ opacity : layerParameters . opacity ,
1024
+ visible : layer . visible ,
1025
+ assets : Object . keys ( layerParameters . data . assets ) ,
1026
+ extent : layerParameters . data . bbox ,
1027
+ } ) ;
1028
+
1029
+ this . setState ( old => ( {
1030
+ ...old ,
1031
+ metadata : layerParameters . data . properties ,
1032
+ } ) ) ;
1033
+
1034
+ break ;
1035
+ }
1036
+ }
985
1037
986
1038
// OpenLayers doesn't have name/id field so add it
987
1039
newMapLayer . set ( 'id' , id ) ;
988
1040
989
- // we need to keep track of which source has which layers
990
- this . _sourceToLayerMap . set ( layerParameters . source , id ) ;
1041
+ // STAC layers don't have source
1042
+ if ( newMapLayer instanceof Layer ) {
1043
+ // we need to keep track of which source has which layers
1044
+ // Only set sourceToLayerMap if 'source' exists on layerParameters
1045
+ if ( 'source' in layerParameters ) {
1046
+ this . _sourceToLayerMap . set ( layerParameters . source , id ) ;
1047
+ }
991
1048
992
- this . addProjection ( newMapLayer ) ;
1049
+ this . addProjection ( newMapLayer ) ;
1050
+ await this . _waitForSourceReady ( newMapLayer ) ;
1051
+ }
993
1052
994
1053
this . _loadingLayers . delete ( id ) ;
995
-
996
1054
return newMapLayer ;
997
1055
}
998
1056
@@ -1005,7 +1063,7 @@ export class MainView extends React.Component<IProps, IStates> {
1005
1063
1006
1064
const projectionCode = sourceProjection . getCode ( ) ;
1007
1065
1008
- const isProjectionRegistered = getRegisteredProjection ( projectionCode ) ;
1066
+ const isProjectionRegistered = getProjection ( projectionCode ) ;
1009
1067
if ( ! isProjectionRegistered ) {
1010
1068
// Check if the projection exists in proj4list
1011
1069
if ( ! proj4list [ projectionCode ] ) {
@@ -1195,16 +1253,6 @@ export class MainView extends React.Component<IProps, IStates> {
1195
1253
mapLayer : Layer ,
1196
1254
oldLayer ?: IDict ,
1197
1255
) : Promise < void > {
1198
- const sourceId = layer . parameters ?. source ;
1199
- const source = this . _model . sharedModel . getLayerSource ( sourceId ) ;
1200
- if ( ! source ) {
1201
- return ;
1202
- }
1203
-
1204
- if ( ! this . _sources [ sourceId ] ) {
1205
- await this . addSource ( sourceId , source ) ;
1206
- }
1207
-
1208
1256
mapLayer . setVisible ( layer . visible ) ;
1209
1257
1210
1258
switch ( layer . type ) {
@@ -1266,6 +1314,9 @@ export class MainView extends React.Component<IProps, IStates> {
1266
1314
1267
1315
break ;
1268
1316
}
1317
+ case 'StacLayer' :
1318
+ mapLayer . setOpacity ( layer . parameters ?. opacity || 1 ) ;
1319
+ break ;
1269
1320
}
1270
1321
}
1271
1322
@@ -1456,7 +1507,7 @@ export class MainView extends React.Component<IProps, IStates> {
1456
1507
* Wait for a layers source state to be 'ready'
1457
1508
* @param layer The Layer to check
1458
1509
*/
1459
- private _waitForSourceReady ( layer : Layer ) {
1510
+ private _waitForSourceReady ( layer : Layer | LayerGroup ) {
1460
1511
return new Promise < void > ( ( resolve , reject ) => {
1461
1512
const checkState = ( ) => {
1462
1513
const state = layer . getSourceState ( ) ;
@@ -1771,10 +1822,6 @@ export class MainView extends React.Component<IProps, IStates> {
1771
1822
const mapLayer = this . getLayer ( id ) ;
1772
1823
const layerTree = JupyterGISModel . getOrderedLayerIds ( this . _model ) ;
1773
1824
1774
- if ( ! mapLayer ) {
1775
- return ;
1776
- }
1777
-
1778
1825
if ( layerTree . includes ( id ) ) {
1779
1826
this . updateLayer ( id , newLayer , mapLayer , oldLayer ) ;
1780
1827
} else {
@@ -1914,6 +1961,10 @@ export class MainView extends React.Component<IProps, IStates> {
1914
1961
extent = tileGrid ?. getExtent ( ) ;
1915
1962
}
1916
1963
1964
+ if ( layer instanceof StacLayer ) {
1965
+ extent = layer . getExtent ( ) ;
1966
+ }
1967
+
1917
1968
if ( ! extent ) {
1918
1969
console . warn ( 'Layer has no extent.' ) ;
1919
1970
return ;
@@ -2165,4 +2216,5 @@ export class MainView extends React.Component<IProps, IStates> {
2165
2216
private _loadingLayers : Set < string > ;
2166
2217
private _originalFeatures : IDict < Feature < Geometry > [ ] > = { } ;
2167
2218
private _highlightLayer : VectorLayer < VectorSource > ;
2219
+ private _updateCenter : CallableFunction ;
2168
2220
}
0 commit comments