diff --git a/CHANGELOG.md b/CHANGELOG.md index 3831a4a0..d3c70222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Client-side validation to login form fields +- Added config option `MAP_ZOOM_MAX` to limit map zooming. +- Added config option `TILE_LAYER_PARAMS` to allow per-collection layer tiling parameters for leaflet. ### Fixed diff --git a/README.md b/README.md index 2b6b5f55..0c89cfa7 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ The file `config_helper/config.example.json` is included in this repository as r | COLLECTIONS | Array of strings listing collections to show in dropdown. This is used to filter the collections endpoint from the list fetched from the `STAC_API_URL` defined in the config. Collection property of `id` must be used. If set, only the matched collections will show in the app. If not set, all collections in the STAC API will show in dropdown. | Optional | | SCENE_TILER_URL | URL for map tiling | Optional | | SCENE_TILER_PARAMS | Per-collection configuration of TiTiler `assets`, `color_formula`, `bidx`, `rescale`, `expression`, `colormap_name`, and `colormap` parameters. Example in [config.example.json](config_helper/config.example.json) | Optional | +| TILE_LAYER_PARAMS | Per-collection configuration of `Leaflet.tileLayer`. See [Leaflet docs](https://leafletjs.com/reference.html#tilelayer-option) or example in [config.example.json](config_helper/config.example.json) | Optional | | MOSAIC_MIN_ZOOM_LEVEL | Minimum zoom level for mosaic view search results. If not set, the default zoom level will be 7. | Optional | | ACTION_BUTTON | Button text and redirect URL used to link to external website as a prominent call to action. If not set, the button will not be visible. Should be an object with `text` and `url` keys. Example: [config.example.json](config_helper/config.example.json). | Optional | | MOSAIC_TILER_URL | URL for mosaic tiling. If not set, the View Mode selector will not be visible. The app requires the use of the [NASA IMPACT TiTiler fork](https://github.com/NASA-IMPACT/titiler) as it contains the mosaicjson endpoints needed. | Optional | @@ -102,6 +103,7 @@ The file `config_helper/config.example.json` is included in this repository as r | 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_ZOOM_MAX | If set, the map zoom level is limited to this integer value. If not set, the default value of `18` 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 | diff --git a/config_helper/config.example.json b/config_helper/config.example.json index 8e1a79c1..ddcaf904 100644 --- a/config_helper/config.example.json +++ b/config_helper/config.example.json @@ -14,6 +14,14 @@ "text": "Action Text Here", "url": "https://redirect-url.example.com" }, + "TILE_LAYER_PARAMS": { + "sentinel-2-l2a": { + "minZoom": 2, + "minNativeZoom": 2, + "maxZoom": 26, + "maxNativeZoom": 18 + } + }, "SCENE_TILER_PARAMS": { "sentinel-2-l2a": { "assets": ["red", "green", "blue"], @@ -159,6 +167,7 @@ "APP_FAVICON": "exampleFavicon.ico", "MAP_ZOOM": 3, "MAP_CENTER": [30, 0], + "MAP_ZOOM_MAX": 24, "LAYER_LIST_ENABLED": true, "LAYER_LIST_SERVICES": [ { diff --git a/src/components/LeafMap/LeafMap.jsx b/src/components/LeafMap/LeafMap.jsx index 6e9fcc96..9deb99f9 100644 --- a/src/components/LeafMap/LeafMap.jsx +++ b/src/components/LeafMap/LeafMap.jsx @@ -18,7 +18,11 @@ import { addReferenceLayersToMap } from '../../utils/mapHelper' import { setScenesForCartLayer } from '../../utils/dataHelper' -import { DEFAULT_MAP_CENTER, DEFAULT_MAP_ZOOM } from '../defaults' +import { + DEFAULT_MAP_CENTER, + DEFAULT_MAP_ZOOM, + DEFAULT_MAP_ZOOM_MAX +} from '../defaults' const LeafMap = () => { const dispatch = useDispatch() @@ -181,6 +185,11 @@ const LeafMap = () => { scrollWheelZoom={true} zoomControl={false} attributionControl={false} + maxZoom={ + _appConfig.MAP_ZOOM_MAX + ? _appConfig.MAP_ZOOM_MAX + : DEFAULT_MAP_ZOOM_MAX + } > {/* set basemap layers here: */} { _appConfig.BASEMAP_URL || 'https://tile.openstreetmap.org/{z}/{x}/{y}.png' } + maxNativeZoom={18} + minNativeZoom={2} + maxZoom={ + _appConfig.MAP_ZOOM_MAX + ? _appConfig.MAP_ZOOM_MAX + : DEFAULT_MAP_ZOOM_MAX + } + minZoom={2} /> diff --git a/src/components/defaults.js b/src/components/defaults.js index e7671b23..a04c20cb 100644 --- a/src/components/defaults.js +++ b/src/components/defaults.js @@ -3,6 +3,7 @@ export const DEFAULT_MOSAIC_MAX_ITEMS = 100 export const DEFAULT_API_MAX_ITEMS = 200 export const DEFAULT_MED_ZOOM = 4 export const DEFAULT_HIGH_ZOOM = 7 +export const DEFAULT_MAP_ZOOM_MAX = 18 export const DEFAULT_COLORMAP = 'viridis' export const DEFAULT_APP_NAME = 'FilmDrop Console' export const DEFAULT_MAX_SCENES_RENDERED = 1000 @@ -11,3 +12,7 @@ export const DEFAULT_MAP_ZOOM = 3 // sets default date range (current minus 24hrs * 60min * 60sec * 1000ms per day * 14 days) const twoWeeksAgo = new Date(Date.now() - 24 * 60 * 60 * 1000 * 14) export const DEFAULT_DATE_RANGE = [twoWeeksAgo, new Date()] +export const DEFAULT_TILE_LAYER_PARAMS = { + tileSize: 256, + pane: 'imagery' +} diff --git a/src/utils/mapHelper.js b/src/utils/mapHelper.js index 5f010259..dc5f9367 100644 --- a/src/utils/mapHelper.js +++ b/src/utils/mapHelper.js @@ -17,7 +17,10 @@ import { searchGridCodeScenes } from './searchHelper' import debounce from './debounce' import { GetMosaicBoundsService } from '../services/get-mosaic-bounds' import GeoJSONValidation from './geojsonValidation' -import { DEFAULT_MOSAIC_MIN_ZOOM } from '../components/defaults' +import { + DEFAULT_MOSAIC_MIN_ZOOM, + DEFAULT_TILE_LAYER_PARAMS +} from '../components/defaults' export const footprintLayerStyle = { color: '#3183f5', @@ -389,13 +392,17 @@ function addImageOverlay(item) { if (sceneTilerURL) { const map = store.getState().mainSlice.map if (map && Object.keys(map).length > 0) { + const collectionTileLayerParams = getTileLayerParams( + _selectedCollectionData.id + ) + const tileLayerParams = { + ...DEFAULT_TILE_LAYER_PARAMS, + ...collectionTileLayerParams, + bounds: tileBounds + } const currentSelectionImageTileLayer = L.tileLayer( `${sceneTilerURL}/stac/tiles/{z}/{x}/{y}@${scale()}x.png?url=${featureURL}&${tilerParams}`, - { - tileSize: 256, - bounds: tileBounds, - pane: 'imagery' - } + tileLayerParams ) .on('load', function () { store.dispatch(setimageOverlayLoading(false)) @@ -429,6 +436,23 @@ function setupBounds(bbox) { return L.latLngBounds(swCorner, neCorner) } +const getTileLayerParams = (collection) => { + const envTileLayerParams = + store.getState().mainSlice.appConfig.TILE_LAYER_PARAMS || '' + if (!envTileLayerParams) { + console.log(`TILE_LAYER_PARAMS is not defined`) + return {} + } + const tileLayerParams = getTilerParams(envTileLayerParams) + + const collectionTileLayerParams = tileLayerParams[collection] || '' + if (!collectionTileLayerParams) { + console.log(`TILE_LAYER_PARAMS not defined for ${collection}`) + return {} + } + return collectionTileLayerParams +} + const constructSceneTilerParams = (collection) => { const envSceneTilerParams = store.getState().mainSlice.appConfig.SCENE_TILER_PARAMS || '' @@ -594,10 +618,11 @@ export async function addMosaicLayer(json) { )?.href GetMosaicBoundsService(baseTileLayerHrefForBounds).then(function (bounds) { const mosaicBounds = leafletBoundsFromBBOX(bounds) + const tileLayerParams = getTileLayerParams(_selectedCollectionData.id) const currentMosaicImageTileLayer = L.tileLayer(mosaicURL, { - tileSize: 256, - bounds: mosaicBounds, - pane: 'imagery' + ...DEFAULT_TILE_LAYER_PARAMS, + ...tileLayerParams, + bounds: mosaicBounds }) .on('load', function () { store.dispatch(setSearchLoading(false))