Skip to content

Commit 01b9072

Browse files
authored
Merge pull request #267 from fieldsoftheworld/hotfixes
2 parents e6c0f10 + aa9064c commit 01b9072

12 files changed

Lines changed: 116 additions & 53 deletions

src/components/GeocodingSearch.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { ref, shallowRef, watch } from 'vue'
33
import { debounce } from 'vuetify/lib/util/helpers.mjs'
44
import type { PlaceResult } from '../composables/useAreaOfInterest'
5-
import { mdiHelpCircleOutline } from '@mdi/js'
5+
import { mdiInformationOutline } from '@mdi/js'
66
77
const emit = defineEmits<{
88
(e: 'location-selected', place: PlaceResult): void
@@ -58,6 +58,8 @@ watch(
5858
const onLocationSelected = (item: { value: PlaceResult; title: string } | null) => {
5959
if (item) {
6060
emit('location-selected', item.value)
61+
placeSearch.value = ''
62+
suggestedPlaces.value = []
6163
}
6264
}
6365
</script>
@@ -69,16 +71,17 @@ const onLocationSelected = (item: { value: PlaceResult; title: string } | null)
6971
v-model:search="placeSearch"
7072
:loading="isLoadingPlaces"
7173
:items="suggestedPlaces"
72-
label="Search for a place"
74+
label="Search a place or address…"
7375
item-title="title"
7476
return-object
7577
hide-details
7678
dense
7779
variant="outlined"
80+
attach
7881
></v-autocomplete>
7982
<v-menu open-on-hover :close-on-content-click="false" max-width="400">
8083
<template #activator="{ props }">
81-
<v-icon class="ml-1" :icon="mdiHelpCircleOutline" size="x-small" v-bind="props"></v-icon>
84+
<v-icon class="ml-1" :icon="mdiInformationOutline" size="x-small" v-bind="props"></v-icon>
8285
</template>
8386
<v-sheet class="pa-3 text-body-2">
8487
Geocoding provided by Nominatim.<br />

src/components/GlobalDataPanel.vue

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<script setup lang="ts">
2-
import { mdiHelpCircleOutline } from '@mdi/js'
2+
import { computed } from 'vue'
3+
import { mdiInformationOutline } from '@mdi/js'
34
import useSettings from '../composables/useSettings'
45
import useMap from '../composables/useMap'
6+
import { getEffectiveCloudlessYear } from '../layers/S2-Cloudless-Layer'
57
import useAreaOfInterest from '../composables/useAreaOfInterest'
68
import type { PlaceResult } from '../composables/useAreaOfInterest'
79
import useNotifier from '../composables/useNotifier'
@@ -12,6 +14,10 @@ import MapLegend from './MapLegend.vue'
1214
1315
const { settings } = useSettings()
1416
const { map } = useMap()
17+
18+
const basemapYearMismatch = computed(
19+
() => settings.value.year !== getEffectiveCloudlessYear(settings.value.year),
20+
)
1521
const { fitToExtent } = useAreaOfInterest()
1622
const { showError } = useNotifier()
1723
useDownloadGrid()
@@ -60,6 +66,16 @@ const handleLocationSelected = (place: PlaceResult) => {
6066
<v-radio label="2024" :value="2024"></v-radio>
6167
<v-radio label="2025" :value="2025"></v-radio>
6268
</v-radio-group>
69+
<v-alert
70+
v-if="basemapYearMismatch"
71+
type="warning"
72+
variant="tonal"
73+
density="compact"
74+
class="mt-2"
75+
>
76+
No basemap is available for {{ settings.year }}. Showing the
77+
{{ getEffectiveCloudlessYear(settings.year) }} basemap instead.
78+
</v-alert>
6379
<h3 class="group">Confidence Threshold: {{ settings.threshold }}%</h3>
6480
<v-slider
6581
v-model.number="settings.threshold"
@@ -86,7 +102,7 @@ const handleLocationSelected = (place: PlaceResult) => {
86102
<h3>Download Data</h3>
87103
<v-menu open-on-hover :close-on-content-click="false" max-width="400">
88104
<template #activator="{ props }">
89-
<v-icon :icon="mdiHelpCircleOutline" size="x-small" v-bind="props"></v-icon>
105+
<v-icon :icon="mdiInformationOutline" size="x-small" v-bind="props"></v-icon>
90106
</template>
91107
<v-sheet class="pa-3 text-body-2">
92108
<p class="pb-2">

src/components/MapComponent.vue

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,25 @@ onMounted(async () => {
4949
layers: [createLabelLayer()],
5050
view: new View({
5151
maxResolution: createXYZ({ tileSize: 512 }).getResolution(0), // use Mapbox/MapLibre compatible resolutions
52+
maxZoom: 16,
5253
center: [0, 0],
5354
zoom: 1,
5455
}),
5556
})
5657
58+
// Initialize the cloudless base layer
59+
initCloudlessLayer()
60+
61+
// Initialize layers
62+
updateLayers()
63+
64+
addMapClickHandler(map.value as Map, areaValues.value)
65+
map.value.on('singleclick', handleMapClick)
66+
// Add scale bar
67+
map.value.addControl(new ScaleLine())
68+
// Setup permalink functionality
69+
setupPermalink(map)
70+
5771
// Get area values and models from API
5872
try {
5973
const token = generateJWT()
@@ -85,22 +99,6 @@ onMounted(async () => {
8599
} catch (error: any) {
86100
critical.value = `Can't connect to server: ${error?.message || error}`
87101
}
88-
89-
// Add layers after map is initialized
90-
if (map.value) {
91-
// Initialize the cloudless base layer
92-
initCloudlessLayer()
93-
94-
// Initialize layers
95-
updateLayers()
96-
97-
addMapClickHandler(map.value as Map, areaValues.value)
98-
map.value.on('singleclick', handleMapClick)
99-
// Add scale bar
100-
map.value.addControl(new ScaleLine())
101-
// Setup permalink functionality
102-
setupPermalink(map)
103-
}
104102
})
105103
106104
// Expose methods to parent components

src/components/ProcessingPanel.vue

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import useAreaOfInterest from '../composables/useAreaOfInterest'
1212
import useProcessingMode from '../composables/useProcessingMode'
1313
import useMap from '../composables/useMap'
1414
import {
15-
mdiHelpCircleOutline,
15+
mdiInformationOutline,
1616
mdiCheckBold,
1717
mdiExclamationThick,
1818
mdiClose,
@@ -23,6 +23,7 @@ import TilePreview from './TilePreview.vue'
2323
import GeocodingSearch from './GeocodingSearch.vue'
2424
import useStacLayer from '../composables/useStacLayer'
2525
import { debounce } from 'vuetify/lib/util/helpers.mjs'
26+
import { getEffectiveCloudlessYear } from '../layers/S2-Cloudless-Layer'
2627
2728
const emit = defineEmits<{
2829
(e: 'workStateChanged', isWorking: boolean): void
@@ -85,6 +86,10 @@ const hasLoadedMore = ref(false)
8586
const sceneSelectionStatus = ref<boolean | null>(null)
8687
const sceneYears = Array.from({ length: 10 }, (_, i) => new Date().getFullYear() - i)
8788
89+
const basemapYearMismatch = computed(
90+
() => settings.value.year !== getEffectiveCloudlessYear(settings.value.year),
91+
)
92+
8893
const isSelectingScenes = computed(
8994
() => sceneSelectionStatus.value === null && settings.value.autoSceneSelection,
9095
)
@@ -475,7 +480,7 @@ defineExpose({ openModelSelection })
475480
<template #activator="{ props }">
476481
<v-icon
477482
class="ml-1"
478-
:icon="mdiHelpCircleOutline"
483+
:icon="mdiInformationOutline"
479484
size="x-small"
480485
v-bind="props"
481486
></v-icon>
@@ -674,6 +679,15 @@ defineExpose({ openModelSelection })
674679
</v-col>
675680
</v-row>
676681

682+
<v-row v-if="basemapYearMismatch">
683+
<v-col>
684+
<v-alert type="warning" variant="tonal" density="compact">
685+
No basemap is available for {{ settings.year }}. Showing the
686+
{{ getEffectiveCloudlessYear(settings.year) }} basemap instead.
687+
</v-alert>
688+
</v-col>
689+
</v-row>
690+
677691
<v-row>
678692
<v-col cols="6">
679693
<v-select

src/components/ProcessingResults.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,19 @@
7777
>
7878
<v-icon :icon="mdiTarget"></v-icon>
7979
</v-btn>
80-
<v-btn class="action-button download-results" @click="downloadResults" density="comfortable"
80+
<v-btn
81+
class="action-button download-results"
82+
@click="downloadResults"
83+
density="comfortable"
84+
title="Export all detected fields as GeoJSON."
8185
>Download</v-btn
8286
>
8387
<v-btn
8488
class="action-button clear-results"
8589
@click="clearResultsHandler"
8690
color="error"
8791
density="compact"
92+
itle="Remove these results from the map."
8893
>Clear</v-btn
8994
>
9095
</div>

src/components/TilePreview.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,16 @@ const handleViewOnMap = async () => {
102102
</div>
103103
<div class="result-details">
104104
<div>Date: {{ tile.date }}</div>
105-
<div v-if="typeof tile.cloudCover === 'number'">
105+
<div
106+
v-if="typeof tile.cloudCover === 'number'"
107+
title="Percent of the scene obscured by clouds."
108+
>
106109
Cloud Cover: {{ tile.cloudCover.toFixed(1) }}%
107110
</div>
108-
<div v-if="typeof tile.areaCoverage === 'number'">
111+
<div
112+
v-if="typeof tile.areaCoverage === 'number'"
113+
title="Percent of the tile area covered by valid data (e.g. not nodata or masked out)."
114+
>
109115
Area Coverage: {{ tile.areaCoverage.toFixed(1) }}%
110116
</div>
111117
</div>

src/composables/useDownloadGrid.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export function featureToGridCell(feature: FeatureLike): GridCell {
4343
function triggerDownload(url: string, filename: string) {
4444
const a = document.createElement('a')
4545
a.href = url
46+
// This doesn't actually set the filename as we download from another host,
47+
// so browsers block providing a custom name (CORS same-origin policy).
4648
a.download = filename
4749
a.rel = 'noopener'
4850
document.body.appendChild(a)

src/composables/useGlobalFeedback.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { toLonLat } from 'ol/proj'
55
import { unByKey } from 'ol/Observable'
66
import type { EventsKey } from 'ol/events'
77
import useNotifier from './useNotifier'
8-
import {
8+
import useSettings, {
99
GLOBAL_DATA_MAP_COMPLETE_ZOOM_LEVEL,
1010
GLOBAL_DATA_MAP_FIELD_START_ZOOM_LEVEL,
1111
} from './useSettings'
@@ -123,6 +123,7 @@ function getEndpoints() {
123123

124124
export default function useGlobalFeedback(mapRef: ShallowRef<Map | null>) {
125125
const { showError, showSuccess } = useNotifier()
126+
const { settings } = useSettings()
126127
const { tileRating: tileRatingEndpoint, tellUsMore: tellUsMoreEndpoint } = getEndpoints()
127128

128129
const sliderValue = ref<number>(1)
@@ -275,6 +276,7 @@ export default function useGlobalFeedback(mapRef: ShallowRef<Map | null>) {
275276
rating: selectedLevel.value,
276277
bbox: mapExtent.value,
277278
resolution: mapResolution.value,
279+
confidence_threshold: settings.value.threshold,
278280
tags: selectedTags.value,
279281
})
280282
return true
@@ -294,12 +296,9 @@ export default function useGlobalFeedback(mapRef: ShallowRef<Map | null>) {
294296
}
295297
}
296298

297-
const openDetailsDialogFromTags = async () => {
298-
const ok = await postRating()
299-
if (ok) {
300-
closeTagsDialog()
301-
openDetailsDialog()
302-
}
299+
const openDetailsDialogFromTags = () => {
300+
closeTagsDialog()
301+
openDetailsDialog()
303302
}
304303

305304
const openDetailsDialog = () => {
@@ -364,6 +363,8 @@ export default function useGlobalFeedback(mapRef: ShallowRef<Map | null>) {
364363
rating: selectedLevel.value,
365364
bbox: mapExtent.value,
366365
resolution: mapResolution.value,
366+
confidence_threshold: settings.value.threshold,
367+
tags: selectedTags.value,
367368
}
368369

369370
if (detailsForm.value.name) {

src/composables/useMap.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { Extent } from 'ol/extent'
1111
import { type FeatureCollection } from 'geojson'
1212
import useNotifier from './useNotifier'
1313
import useSettings from './useSettings'
14-
import createCloudlessLayer from '../layers/S2-Cloudless-Layer'
14+
import createCloudlessLayer, { getEffectiveCloudlessYear } from '../layers/S2-Cloudless-Layer'
1515
import createS2GridLayer from '../layers/S2-Grid-Layer'
1616
import {
1717
createGlobalPredictionsLayer,
@@ -71,11 +71,16 @@ const cloudlessLayer = shallowRef<TileLayer<XYZ> | null>(null)
7171
// Watch for year changes and update the cloudless layer
7272
watch(
7373
() => settings.value.year,
74-
(newYear) => {
74+
(newYear, oldYear) => {
7575
if (!map.value) {
7676
return
7777
}
7878

79+
// Skip reload when the effective (clamped) year hasn't changed (e.g. 2024 -> 2025)
80+
if (getEffectiveCloudlessYear(newYear) === getEffectiveCloudlessYear(oldYear)) {
81+
return
82+
}
83+
7984
// Remove the old cloudless layer if it exists
8085
if (cloudlessLayer.value) {
8186
map.value.removeLayer(cloudlessLayer.value)

src/layers/S2-Cloudless-Layer.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import TileLayer from 'ol/layer/Tile'
22
import { XYZ } from 'ol/source'
33

4+
export const MIN_CLOUDLESS_YEAR = 2016
5+
export const MAX_CLOUDLESS_YEAR = 2024
6+
7+
export function getEffectiveCloudlessYear(year: number): number {
8+
return Math.min(MAX_CLOUDLESS_YEAR, Math.max(MIN_CLOUDLESS_YEAR, year))
9+
}
10+
411
export default function createCloudlessLayer(year: number) {
5-
if (year < 2016) {
6-
year = 2016
7-
} else if (year > 2024) {
8-
year = 2024
9-
}
12+
year = getEffectiveCloudlessYear(year)
1013
return new TileLayer({
1114
source: new XYZ({
1215
url: `https://tiles.maps.eox.at/wmts?layer=s2cloudless${year === 2016 ? '' : '-' + year}_3857&style=default&tilematrixset=GoogleMapsCompatible&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fjpeg&TileMatrix={z}&TileCol={x}&TileRow={y}`,

0 commit comments

Comments
 (0)