Skip to content

Commit

Permalink
Merge pull request #256 from Element84/ba/feature/adding-layer-list
Browse files Browse the repository at this point in the history
Ba/feature/adding layer list
  • Loading branch information
bradleyandrick authored Sep 14, 2023
2 parents 8c7b158 + e1ec097 commit 75726a5
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 8 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## Unreleased

### Added

- Added config option for starting map zoom level
- Added config option for starting map center location
- Added config options for enabling and defining reference map layers (only wms currently supported)

## 4.0.1 - 2023-09-12

### Changed
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ The file `./public/config/config.example.json` is included in this repository as
| POPUP_DISPLAY_FIELDS | Per-collection configuration of popup metadata fields properies to render. Example in [config.example.json](./public/config/config.example.json). Only `Title` field (which maps to the `id` property for STAC items) is rendered if collection used in application but not included in configuration. | Optional |
| APP_NAME | String value used for html title and anywhere else that the text value for app name is used. If not set, default value of `FilmDrop Console` will be used. | Optional |
| APP_FAVICON | If set, custom application favicon is used instead of default FilmDrop favicon. Favicon file of format `.ico` OR `.png` must be used and file must exist next to config in `/config` of the built deployment directory. Place in `public` directory during local development, but can also be added or adjusted post depolyment. File name in `config.json` must match extactly with file in config, see `config.example.json` for example. If not set or error in config/file, default FilmDrop favicon will be used. | Optional |
| MAP_ZOOM | If set, starting map zoom level is set to this integer value. If not set, default value of `3` will be used. | Optional |
| MAP_CENTER | If set, starting map center point is initialized with this location. If not set, default map location of `[30, 0]` will be used. | Optional |
| LAYER_LIST_ENABLED | If set to `true`, reference layer list widget is displayed in map controls. NOTE: both `LAYER_LIST_ENABLED` and `LAYER_LIST_SERVICES` must exist in config for reference layer list widget to actually be displayed in the UI. If not set or `false`, reference layer list widget is not rendered. | Optional |
| LAYER_LIST_SERVICES | Defines the services used as reference layers for the map. **Limitations:** Currently only WMS services are supported and only `EPSG:4326` or `EPSG:3857` are supported values for defining crs options. If not set or not formatted correctly, reference layer list widget will either be empty or will not render. Formatting should match example in `config.example.json`. | Optional |

### Links

Expand Down
37 changes: 36 additions & 1 deletion public/config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,40 @@
]
},
"APP_NAME": "Filmdrop Console",
"APP_FAVICON": "exampleFavicon.ico"
"APP_FAVICON": "exampleFavicon.ico",
"MAP_ZOOM": 3,
"MAP_CENTER": [30, 0],
"LAYER_LIST_ENABLED": true,
"LAYER_LIST_SERVICES": [
{
"name": "Service 1",
"type": "wms",
"url": "https://sampleservice1.com/wms",
"layers": [
{
"name": "layer1_name",
"alias": "Layer 1 Alias",
"default_visibility": true,
"crs": "EPSG:4326"
},
{
"name": "layer2_name",
"alias": "Layer 2 Alias",
"default_visibility": false
}
]
},
{
"name": "Service 2",
"type": "wms",
"url": "https://sampleservice2.com/wms",
"layers": [
{
"name": "layer1_name",
"alias": "Layer 1 Alias",
"default_visibility": true
}
]
}
]
}
98 changes: 98 additions & 0 deletions src/components/LayerList/LayerList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
.LayerList {
position: absolute;
top: 130px;
left: 50px;
z-index: 1;
background-color: rgba(34, 34, 34, 0.8);
border-radius: 2px;
min-width: 200px;
max-width: 400px;
max-height: 300px;
font-size: 16px;
display: flex;
flex-direction: column;
}

.LayerListTitle {
border-bottom: solid 2px #373d4d;
height: 35px;
margin-bottom: 8px;
display: flex;
align-items: center;
}

.LayerListTitleText {
margin-left: 15px;
}

.LayerListLayers {
overflow-y: auto;
margin-left: 14px;
margin-bottom: 5px;
}

.LayerListLayer {
display: flex;
flex-direction: row;
}

.LayerListLayerContainer {
display: block;
position: relative;
padding-left: 25px;
margin-bottom: 12px;
margin-right: 15px;
cursor: pointer;
font-size: 14px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.LayerListLayerContainer input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}

.LayerListCheckmark {
position: absolute;
top: 0;
left: 0;
height: 16px;
width: 16px;
background-color: #eee;
}

.LayerListLayerContainer:hover input ~ .LayerListCheckmark {
background-color: #ccc;
}

.LayerListLayerContainer input:checked ~ .LayerListCheckmark {
background-color: #81c05b;
}

.LayerListCheckmark:after {
content: '';
position: absolute;
display: none;
}

.LayerListLayerContainer input:checked ~ .LayerListCheckmark:after {
display: block;
}

.LayerListLayerContainer .LayerListCheckmark:after {
left: 5px;
top: 1px;
width: 4px;
height: 8px;
border: solid #fff;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
46 changes: 46 additions & 0 deletions src/components/LayerList/LayerList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import './LayerList.css'
import { useSelector, useDispatch } from 'react-redux'
import { setreferenceLayers } from '../../redux/slices/mainSlice'
import { toggleReferenceLayerVisibility } from '../../utils/mapHelper'

const LayerList = () => {
const dispatch = useDispatch()
const _referenceLayers = useSelector(
(state) => state.mainSlice.referenceLayers
)
function onLayerClicked(combinedLayerName) {
const updatedLayers = _referenceLayers.map((layer) =>
layer.combinedLayerName === combinedLayerName
? { ...layer, visibility: !layer.visibility }
: layer
)
dispatch(setreferenceLayers(updatedLayers))
toggleReferenceLayerVisibility(combinedLayerName)
}

return (
<div className="LayerList">
<div className="LayerListTitle">
<span className="LayerListTitleText">Reference Layers</span>
</div>
<div className="LayerListLayers">
{_referenceLayers.map((layer) => (
<div className="LayerListLayer" key={layer.combinedLayerName}>
<label className="LayerListLayerContainer">
{layer.layerAlias}
<input
type="checkbox"
checked={layer.visibility}
onChange={() => onLayerClicked(layer.combinedLayerName)}
></input>
<span className="LayerListCheckmark"></span>
</label>
</div>
))}
</div>
</div>
)
}

export default LayerList
24 changes: 24 additions & 0 deletions src/components/Layout/Content/BottomContent/BottomContent.css
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,30 @@ div#attribution-tooltip {
background-color: #12171a;
}

.layerListButton {
position: absolute;
background-color: #fff;
top: 130px;
left: 12px;
z-index: 1;
height: 29px;
width: 29px;
cursor: pointer;
border-radius: 4px;
border: solid 1px #ccc;
display: flex;
align-items: center;
justify-content: center;
}

.layerListButton:hover {
background-color: #f4f4f4;
}

.layerListButtonIcon {
color: #555;
}

@media screen and (max-width: 1460px) {
.BottomContent {
height: calc(100% - 200px);
Expand Down
19 changes: 18 additions & 1 deletion src/components/Layout/Content/BottomContent/BottomContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
setisDrawingEnabled,
setmappedScenes,
setSearchLoading,
setshowMapAttribution
setshowMapAttribution,
setshowLayerList
} from '../../../../redux/slices/mainSlice'
import {
setMapZoomLevel,
Expand All @@ -30,6 +31,8 @@ import { CircularProgress } from '@mui/material'
import DOMPurify from 'dompurify'
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'
import { Tooltip } from 'react-tooltip'
import LayersIcon from '@mui/icons-material/Layers'
import LayerList from '../../../LayerList/LayerList'

const BottomContent = () => {
const [allScenesLoading, setallScenesLoading] = useState(false)
Expand Down Expand Up @@ -64,6 +67,7 @@ const BottomContent = () => {
(state) => state.mainSlice.imageOverlayLoading
)
const _appName = useSelector((state) => state.mainSlice.appName)
const _showLayerList = useSelector((state) => state.mainSlice.showLayerList)

const dispatch = useDispatch()

Expand Down Expand Up @@ -187,6 +191,10 @@ const BottomContent = () => {
dispatch(setshowMapAttribution(false))
}

function onLayerListButtonClick() {
dispatch(setshowLayerList(!_showLayerList))
}

return (
<div className="BottomContent">
<LeafMap></LeafMap>
Expand All @@ -198,6 +206,15 @@ const BottomContent = () => {
</Box>
</div>
)}
{_appConfig.LAYER_LIST_ENABLED && _appConfig.LAYER_LIST_SERVICES && (
<div className="layerListButton" title="Layer List">
<LayersIcon
className="layerListButtonIcon"
onClick={() => onLayerListButtonClick()}
></LayersIcon>
</div>
)}
{_showLayerList && <LayerList></LayerList>}
<div className="actionButtons">
{_appConfig.ANALYZE_BTN_URL && (
<button className="actionButton" onClick={() => onAnalyzeClick()}>
Expand Down
15 changes: 12 additions & 3 deletions src/components/LeafMap/LeafMap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import 'leaflet-geosearch/dist/geosearch.css'
import {
mapClickHandler,
mapCallDebounceNewSearch,
setMosaicZoomMessage
setMosaicZoomMessage,
addReferenceLayersToMap
} from '../../utils/mapHelper'
import { setScenesForCartLayer } from '../../utils/dataHelper'
import { DEFAULT_MAP_CENTER, DEFAULT_MAP_ZOOM } from '../defaults'

const LeafMap = () => {
const dispatch = useDispatch()
Expand Down Expand Up @@ -93,6 +95,9 @@ const LeafMap = () => {
})

// set up map layers
const referenceLayerGroup = L.layerGroup().addTo(map)
referenceLayerGroup.layer_name = 'referenceLayerGroup'

const resultFootprintsInit = new L.FeatureGroup()
resultFootprintsInit.addTo(map)
resultFootprintsInit.layer_name = 'searchResultsLayer'
Expand Down Expand Up @@ -164,6 +169,8 @@ const LeafMap = () => {

// push map into redux state
dispatch(setMap(map))

addReferenceLayersToMap()
}
}, [map])

Expand All @@ -173,8 +180,10 @@ const LeafMap = () => {
<MapContainer
className="mainMap"
ref={mapRef}
center={[30, 0]}
zoom={3}
center={
_appConfig.MAP_CENTER ? _appConfig.MAP_CENTER : DEFAULT_MAP_CENTER
}
zoom={_appConfig.MAP_ZOOM ? _appConfig.MAP_ZOOM : DEFAULT_MAP_ZOOM}
scrollWheelZoom={true}
zoomControl={false}
attributionControl={false}
Expand Down
2 changes: 2 additions & 0 deletions src/components/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export const DEFAULT_HIGH_ZOOM = 7
export const DEFAULT_COLORMAP = 'viridis'
export const DEFAULT_APP_NAME = 'FilmDrop Console'
export const DEFAULT_MAX_SCENES_RENDERED = 1000
export const DEFAULT_MAP_CENTER = [30, 0]
export const DEFAULT_MAP_ZOOM = 3
12 changes: 11 additions & 1 deletion src/redux/slices/mainSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ const initialState = {
mappedScenes: [],
imageOverlayLoading: false,
showMapAttribution: true,
appName: ''
appName: '',
showLayerList: false,
referenceLayers: []
}

// next, for every key in the initialState
Expand Down Expand Up @@ -157,6 +159,12 @@ export const mainSlice = createSlice({
},
setappName: (state, action) => {
state.appName = action.payload
},
setshowLayerList: (state, action) => {
state.showLayerList = action.payload
},
setreferenceLayers: (state, action) => {
state.referenceLayers = action.payload
}
}
})
Expand Down Expand Up @@ -200,5 +208,7 @@ export const { setmappedScenes } = mainSlice.actions
export const { setimageOverlayLoading } = mainSlice.actions
export const { setshowMapAttribution } = mainSlice.actions
export const { setappName } = mainSlice.actions
export const { setshowLayerList } = mainSlice.actions
export const { setreferenceLayers } = mainSlice.actions

export default mainSlice.reducer
Loading

0 comments on commit 75726a5

Please sign in to comment.