@@ -22,6 +22,7 @@ import {
2222  IRasterLayer , 
2323  IRasterSource , 
2424  IShapefileSource , 
25+   IStacLayer , 
2526  IVectorLayer , 
2627  IVectorTileLayer , 
2728  IVectorTileSource , 
@@ -51,15 +52,15 @@ import {
5152  VectorTile  as  VectorTileLayer , 
5253  WebGLTile  as  WebGlTileLayer , 
5354}  from  'ol/layer' ; 
55+ import  LayerGroup  from  'ol/layer/Group' ; 
5456import  TileLayer  from  'ol/layer/Tile' ; 
5557import  { 
5658  fromLonLat , 
57-   get  as  getRegisteredProjection , 
59+   get  as  getProjection , 
5860  toLonLat , 
5961  transformExtent , 
6062}  from  'ol/proj' ; 
6163import  {  register  }  from  'ol/proj/proj4.js' ; 
62- import  {  get  as  getProjection  }  from  'ol/proj.js' ; 
6364import  RenderFeature  from  'ol/render/Feature' ; 
6465import  { 
6566  GeoTIFF  as  GeoTIFFSource , 
@@ -74,6 +75,7 @@ import { Circle, Fill, Stroke, Style } from 'ol/style';
7475import  {  Rule  }  from  'ol/style/flat' ; 
7576//@ts -expect-error no types for ol-pmtiles 
7677import  {  PMTilesRasterSource ,  PMTilesVectorSource  }  from  'ol-pmtiles' ; 
78+ import  StacLayer  from  'ol-stac' ; 
7779import  proj4  from  'proj4' ; 
7880import  proj4list  from  'proj4-list' ; 
7981import  *  as  React  from  'react' ; 
@@ -82,12 +84,21 @@ import AnnotationFloater from '@/src/annotations/components/AnnotationFloater';
8284import  {  CommandIDs  }  from  '@/src/constants' ; 
8385import  {  LoadingOverlay  }  from  '@/src/shared/components/loading' ; 
8486import  StatusBar  from  '@/src/statusbar/StatusBar' ; 
85- import  {  isLightTheme ,  loadFile ,  throttle  }  from  '@/src/tools' ; 
87+ import  {  debounce ,   isLightTheme ,  loadFile ,  throttle  }  from  '@/src/tools' ; 
8688import  CollaboratorPointers ,  {  ClientPointer  }  from  './CollaboratorPointers' ; 
8789import  {  FollowIndicator  }  from  './FollowIndicator' ; 
8890import  TemporalSlider  from  './TemporalSlider' ; 
8991import  {  MainViewModel  }  from  './mainviewmodel' ; 
9092
93+ type  OlLayerTypes  = 
94+   |  TileLayer 
95+   |  VectorLayer 
96+   |  VectorTileLayer 
97+   |  WebGlTileLayer 
98+   |  WebGlTileLayer 
99+   |  HeatmapLayer 
100+   |  StacLayer 
101+   |  ImageLayer < any > ; 
91102interface  IProps  { 
92103  viewModel : MainViewModel ; 
93104} 
@@ -205,6 +216,7 @@ export class MainView extends React.Component<IProps, IStates> {
205216    this . _contextMenu  =  new  ContextMenu ( { 
206217      commands : this . _commands , 
207218    } ) ; 
219+     this . _updateCenter  =  debounce ( this . updateCenter ,  100 ) ; 
208220  } 
209221
210222  async  componentDidMount ( ) : Promise < void >  { 
@@ -302,6 +314,8 @@ export class MainView extends React.Component<IProps, IStates> {
302314
303315      const  view  =  this . _Map . getView ( ) ; 
304316
317+       view . on ( 'change:center' ,  ( )  =>  this . _updateCenter ( ) ) ; 
318+ 
305319      // TODO: Note for the future, will need to update listeners if view changes 
306320      view . on ( 
307321        'change:center' , 
@@ -409,6 +423,22 @@ export class MainView extends React.Component<IProps, IStates> {
409423    } 
410424  } 
411425
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+ 
412442  createSelectInteraction  =  ( )  =>  { 
413443    const  pointStyle  =  new  Style ( { 
414444      image : new  Circle ( { 
@@ -870,24 +900,28 @@ export class MainView extends React.Component<IProps, IStates> {
870900  private  async  _buildMapLayer ( 
871901    id : string , 
872902    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 >  { 
880904    this . setState ( old  =>  ( {  ...old ,  loadingLayer : true  } ) ) ; 
881905    this . _loadingLayers . add ( id ) ; 
882906
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 ; 
886911
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+     } 
891925
892926    // TODO: OpenLayers provides a bunch of sources for specific tile 
893927    // providers, so maybe set up some way to use those 
@@ -977,22 +1011,46 @@ export class MainView extends React.Component<IProps, IStates> {
9771011          radius : layerParameters . radius  ??  8 , 
9781012          gradient : layerParameters . color , 
9791013        } ) ; 
1014+ 
9801015        break ; 
9811016      } 
982-     } 
1017+       case  'StacLayer' : { 
1018+         layerParameters  =  layer . parameters  as  IStacLayer ; 
9831019
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+     } 
9851037
9861038    // OpenLayers doesn't have name/id field so add it 
9871039    newMapLayer . set ( 'id' ,  id ) ; 
9881040
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+       } 
9911048
992-     this . addProjection ( newMapLayer ) ; 
1049+       this . addProjection ( newMapLayer ) ; 
1050+       await  this . _waitForSourceReady ( newMapLayer ) ; 
1051+     } 
9931052
9941053    this . _loadingLayers . delete ( id ) ; 
995- 
9961054    return  newMapLayer ; 
9971055  } 
9981056
@@ -1005,7 +1063,7 @@ export class MainView extends React.Component<IProps, IStates> {
10051063
10061064    const  projectionCode  =  sourceProjection . getCode ( ) ; 
10071065
1008-     const  isProjectionRegistered  =  getRegisteredProjection ( projectionCode ) ; 
1066+     const  isProjectionRegistered  =  getProjection ( projectionCode ) ; 
10091067    if  ( ! isProjectionRegistered )  { 
10101068      // Check if the projection exists in proj4list 
10111069      if  ( ! proj4list [ projectionCode ] )  { 
@@ -1195,16 +1253,6 @@ export class MainView extends React.Component<IProps, IStates> {
11951253    mapLayer : Layer , 
11961254    oldLayer ?: IDict , 
11971255  ) : 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- 
12081256    mapLayer . setVisible ( layer . visible ) ; 
12091257
12101258    switch  ( layer . type )  { 
@@ -1266,6 +1314,9 @@ export class MainView extends React.Component<IProps, IStates> {
12661314
12671315        break ; 
12681316      } 
1317+       case  'StacLayer' :
1318+         mapLayer . setOpacity ( layer . parameters ?. opacity  ||  1 ) ; 
1319+         break ; 
12691320    } 
12701321  } 
12711322
@@ -1456,7 +1507,7 @@ export class MainView extends React.Component<IProps, IStates> {
14561507   * Wait for a layers source state to be 'ready' 
14571508   * @param  layer The Layer to check 
14581509   */ 
1459-   private  _waitForSourceReady ( layer : Layer )  { 
1510+   private  _waitForSourceReady ( layer : Layer   |   LayerGroup )  { 
14601511    return  new  Promise < void > ( ( resolve ,  reject )  =>  { 
14611512      const  checkState  =  ( )  =>  { 
14621513        const  state  =  layer . getSourceState ( ) ; 
@@ -1771,10 +1822,6 @@ export class MainView extends React.Component<IProps, IStates> {
17711822      const  mapLayer  =  this . getLayer ( id ) ; 
17721823      const  layerTree  =  JupyterGISModel . getOrderedLayerIds ( this . _model ) ; 
17731824
1774-       if  ( ! mapLayer )  { 
1775-         return ; 
1776-       } 
1777- 
17781825      if  ( layerTree . includes ( id ) )  { 
17791826        this . updateLayer ( id ,  newLayer ,  mapLayer ,  oldLayer ) ; 
17801827      }  else  { 
@@ -1914,6 +1961,10 @@ export class MainView extends React.Component<IProps, IStates> {
19141961      extent  =  tileGrid ?. getExtent ( ) ; 
19151962    } 
19161963
1964+     if  ( layer  instanceof  StacLayer )  { 
1965+       extent  =  layer . getExtent ( ) ; 
1966+     } 
1967+ 
19171968    if  ( ! extent )  { 
19181969      console . warn ( 'Layer has no extent.' ) ; 
19191970      return ; 
@@ -2165,4 +2216,5 @@ export class MainView extends React.Component<IProps, IStates> {
21652216  private  _loadingLayers : Set < string > ; 
21662217  private  _originalFeatures : IDict < Feature < Geometry > [ ] >  =  { } ; 
21672218  private  _highlightLayer : VectorLayer < VectorSource > ; 
2219+   private  _updateCenter : CallableFunction ; 
21682220} 
0 commit comments