diff --git a/app/public/data/esa/globalDataLayerImages/World-E13c_ship_detections.png b/app/public/data/esa/globalDataLayerImages/World-E13c_ship_detections.png
new file mode 100644
index 0000000000..1d502405b1
Binary files /dev/null and b/app/public/data/esa/globalDataLayerImages/World-E13c_ship_detections.png differ
diff --git a/app/public/eodash-data/stories/E13c_ship_detections.md b/app/public/eodash-data/stories/E13c_ship_detections.md
new file mode 100644
index 0000000000..48cdce8528
--- /dev/null
+++ b/app/public/eodash-data/stories/E13c_ship_detections.md
@@ -0,0 +1 @@
+placeholder
diff --git a/app/src/components/map/CustomAreaButtons.vue b/app/src/components/map/CustomAreaButtons.vue
index b9b0191c3b..45210412d7 100644
--- a/app/src/components/map/CustomAreaButtons.vue
+++ b/app/src/components/map/CustomAreaButtons.vue
@@ -86,7 +86,6 @@ import Feature from 'ol/Feature';
import {
mapState,
} from 'vuex';
-// import { getArea } from 'ol/extent';
import Text from 'ol/style/Text';
import { Polygon } from 'ol/geom';
@@ -244,12 +243,14 @@ export default {
* @param {*} geom OpenLayer geometry
* @returns {Boolean}
*/
- isGeometryTooLarge(geom) { // eslint-disable-line
- // for now commenting out previous logic
+ isGeometryTooLarge(geom) {
+ if (this.mergedConfigsData.length && this.mergedConfigsData[0]?.maxDrawnAreaSide) {
+ const extent = geom.getExtent();
+ return extent && (
+ (extent[3] - extent[1] > this.mergedConfigsData[0]?.maxDrawnAreaSide)
+ || (extent[2] - extent[0] > this.mergedConfigsData[0]?.maxDrawnAreaSide));
+ }
return false;
- // const extent = geom.getExtent();
- // to do: use more exact turf calculations?
- // return extent && (getArea(extent) > 50000000000);
},
onDrawFinished(event) {
const { map } = getMapInstance(this.mapId);
diff --git a/app/src/components/map/CustomFeaturesFetchButton.vue b/app/src/components/map/CustomFeaturesFetchButton.vue
new file mode 100644
index 0000000000..6994b9fb25
--- /dev/null
+++ b/app/src/components/map/CustomFeaturesFetchButton.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+ mdi-map-search-outline
+
+
+ Get detections
+
+
+
+
+
+
+
diff --git a/app/src/components/map/DatePickerControl.vue b/app/src/components/map/DatePickerControl.vue
new file mode 100644
index 0000000000..a5ae307269
--- /dev/null
+++ b/app/src/components/map/DatePickerControl.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+ mdi-calendar
+
+
+ Choose date
+
+
+
+
+
+
+
+
diff --git a/app/src/components/map/Map.vue b/app/src/components/map/Map.vue
index d51e22dda6..3679b12d78 100644
--- a/app/src/components/map/Map.vue
+++ b/app/src/components/map/Map.vue
@@ -127,6 +127,24 @@
:key="dataLayerName + '_customArea'"
:drawnArea.sync="drawnArea"
/>
+
+
+
config.timeFromProperty || config.usedTimes?.time?.length,
+ (config) => config.mapTimeDatepicker
+ || config.timeFromProperty
+ || config.usedTimes?.time?.length,
)
.forEach((config) => {
const layer = layers.find((l) => l.get('name') === config.name);
- if (layer) {
+ if (layer instanceof VectorLayer && config.mapTimeDatepicker) {
+ // do not fetch new features on time changeempty
+ } else if (layer) {
updateTimeLayer(layer, config, timeObj.value, area);
}
});
@@ -795,6 +824,12 @@ export default {
this.$emit('update:center', e);
this.currentCenter = e;
},
+ setDateFromDatePicker(date) {
+ this.dataLayerTime = {
+ name: date,
+ value: DateTime.fromISO(date),
+ };
+ },
updateTime(time, compare) {
// Define a function to update the data layer
// direct match on name
@@ -967,17 +1002,25 @@ export default {
}
}
},
- updateSelectedAreaFeature() {
+ updateSelectedAreaFeature(manualTrigger = false) {
const { map } = getMapInstance(this.mapId);
const dataGroup = map.getLayers().getArray().find((l) => l.get('id') === 'dataGroup');
const layers = dataGroup.getLayers().getArray();
const area = this.drawnArea;
const time = this.dataLayerTime?.value;
- this.mergedConfigsDataIndexAware.filter((config) => config.usedTimes?.time?.length)
+ this.mergedConfigsDataIndexAware.filter(
+ (config) => config.mapTimeDatepicker || config.usedTimes?.time?.length,
+ )
.forEach((config) => {
const layer = layers.find((l) => l.get('name') === config.name);
if (layer) {
- updateTimeLayer(layer, config, time, area, 'updateArea');
+ if (manualTrigger) {
+ updateTimeLayer(layer, config, time, area, 'updateArea');
+ } else if (layer instanceof VectorLayer && config.mapTimeDatepicker) {
+ // do nothing
+ } else {
+ updateTimeLayer(layer, config, time, area, 'updateArea');
+ }
}
});
},
diff --git a/app/src/components/map/SliderControl.vue b/app/src/components/map/SliderControl.vue
new file mode 100644
index 0000000000..6b7004790b
--- /dev/null
+++ b/app/src/components/map/SliderControl.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+ mdi-dots-horizontal
+
+
+ {{config.title}}
+
+
+
+ {{config.title}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/config/esa.js b/app/src/config/esa.js
index d9186ee826..9f547d0430 100644
--- a/app/src/config/esa.js
+++ b/app/src/config/esa.js
@@ -455,6 +455,10 @@ export const indicatorsDefinition = Object.freeze({
themes: ['economy'],
story: '',
},
+ E13c_ship_detections: {
+ themes: ['economy'],
+ story: '/eodash-data/stories/E13c_ship_detections',
+ },
E13b: {
indicatorSummary: 'Throughput at principal hub airports',
themes: ['economy'],
@@ -1818,6 +1822,66 @@ export const globalIndicators = [
},
},
},
+ {
+ properties: {
+ indicatorObject: {
+ dataLoadFinished: true,
+ country: 'all',
+ city: 'World',
+ siteName: 'global',
+ description: 'Ship detection',
+ indicator: 'E13c_ship_detections',
+ indicatorName: 'Ships-detection on-the-fly',
+ subAoi: {
+ type: 'FeatureCollection',
+ features: [],
+ },
+ aoiID: 'World',
+ time: [],
+ inputData: [''],
+ yAxis: '',
+ display: [{
+ baseLayers: cloudlessBaseLayerDefault,
+ disableCompare: true,
+ dateFormatFunction: (date) => `${DateTime.fromISO(date).toFormat('yyyy-MM-dd')}/${DateTime.fromISO(date).toFormat('yyyy-MM-dd')}`,
+ layers: 'SENTINEL-2-L2A-TRUE-COLOR',
+ name: 'Daily Sentinel 2 L2A',
+ // 2500 pixel SH limit * 10 m resolution of S2 RGB bands
+ // and multiplied by 4/5 to cater for slowness of algorithm and data transfer
+ maxDrawnAreaSide: 20000,
+ minZoom: 7,
+ maxZoom: 18,
+ mapTimeDatepicker: true,
+ sliderConfig: {
+ title: 'Detection Threshold',
+ min: 0,
+ max: 1,
+ step: 0.01,
+ default: 0.5,
+ },
+ drawnAreaLimitExtent: true,
+ // areaIndicator: trucksAreaIndicator,
+ features: {
+ url: 'https://gtif-backend.hub.eox.at/ship_detection?{area}&{featuresTime}&threshold={sliderValue}',
+ name: 'Ship detections on-the-fly',
+ style: {
+ strokeColor: '#00c3ff',
+ width: 2,
+ },
+ dateFormatFunction: (date) => `start_date=${DateTime.fromISO(date).toFormat('yyyy-MM-dd')}&end_date=${DateTime.fromISO(date).toFormat('yyyy-MM-dd')}`,
+ areaFormatFunction: (area) => {
+ const extent = geojsonFormat.readGeometry(area).getExtent();
+ const formattedArea = `lon_min=${extent[0]}&lat_min=${extent[1]}&lon_max=${extent[2]}&lat_max=${extent[3]}`;
+ return {
+ area: formattedArea,
+ };
+ },
+ },
+ customAreaFeatures: true,
+ }],
+ },
+ },
+ },
{
properties: {
indicatorObject: {
diff --git a/app/src/helpers/customAreaObjects.js b/app/src/helpers/customAreaObjects.js
index 2d215283fc..9086e66ec3 100644
--- a/app/src/helpers/customAreaObjects.js
+++ b/app/src/helpers/customAreaObjects.js
@@ -29,6 +29,9 @@ export const fetchCustomDataOptions = (time, sourceOptionsObj, store) => {
);
outputOptionsObj.site = currSite;
}
+ if (store.state.features.sliderValue) {
+ outputOptionsObj.sliderValue = store.state.features.sliderValue;
+ }
if (time) {
// substitutes {time} template possibly utilizing dateFormatFunction
@@ -440,6 +443,7 @@ export const fetchCustomAreaObjects = async (
return custom;
});
} else {
+ window.dispatchEvent(new CustomEvent('set-custom-area-features-loading', { detail: true }));
customObjects = await fetch(url, requestOpts).then((response) => {
if (!response.ok) {
return response.text().then((text) => { throw text; });
@@ -494,6 +498,9 @@ export const fetchCustomAreaObjects = async (
// If error message is an object it is probably the returned html
console.log('Possible issue retrieving geoJSON for specified time');
}
+ })
+ .finally(() => {
+ window.dispatchEvent(new CustomEvent('set-custom-area-features-loading', { detail: false }));
});
}
return customObjects;
diff --git a/app/src/store/modules/features.js b/app/src/store/modules/features.js
index 1e0962f114..72e54b4e33 100644
--- a/app/src/store/modules/features.js
+++ b/app/src/store/modules/features.js
@@ -18,6 +18,8 @@ const state = {
custom: [],
},
selectedArea: null,
+ selectedCalendarDate: null,
+ sliderValue: null,
selectedFeatures: [],
};
@@ -291,6 +293,12 @@ const mutations = {
SET_SELECTED_AREA(state, area) {
state.selectedArea = area;
},
+ SET_SELECTED_DATE(state, date) {
+ state.selectedCalendarDate = date;
+ },
+ SET_SLIDER_VALUE(state, sliderValue) {
+ state.sliderValue = sliderValue;
+ },
SET_ADMIN_BORDER_FEATURE_SELECTED(state, feature) {
state.adminBorderFeatureSelected = feature;
},