Skip to content

Commit

Permalink
Merge pull request #53 from mapswipe/feature/street
Browse files Browse the repository at this point in the history
Street level image classification project type
  • Loading branch information
ofr1tz authored Dec 9, 2024
2 parents 3688201 + 3e3c6d9 commit 44e454c
Show file tree
Hide file tree
Showing 14 changed files with 577 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .env.development.local.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_MEASUREMENT_ID=

VITE_MAPILLARY_API_KEY=
2 changes: 2 additions & 0 deletions .env.production.local.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_MEASUREMENT_ID=

VITE_MAPILLARY_API_KEY=
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@intlify/unplugin-vue-i18n": "^3.0.1",
"base-64": "^1.0.0",
"firebase": "^10.9.0",
"mapillary-js": "^4.1.2",
"ol": "^9.1.0",
"ol-contextmenu": "^5.4.0",
"ol-ext": "^4.0.17",
Expand Down
2 changes: 1 addition & 1 deletion src/components/DigitizeProject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default defineComponent({
},
tutorial: {
type: Object,
require: false,
required: false,
},
},
data() {
Expand Down
7 changes: 4 additions & 3 deletions src/components/MediaProject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default defineComponent({
},
tutorial: {
type: Object,
require: false,
required: false,
},
},
data() {
Expand Down Expand Up @@ -93,7 +93,7 @@ export default defineComponent({
},
back() {
if (!this.taskIndex <= 0) {
this.imageLoaded = false
this.isImageLoaded = false
this.taskIndex--
this.taskId = this.tasks[this.taskIndex].taskId
}
Expand All @@ -105,7 +105,7 @@ export default defineComponent({
},
forward() {
if (this.isImageLoaded && this.isAnswered() && this.taskIndex + 1 < this.tasks.length) {
this.imageLoaded = false
this.isImageLoaded = false
this.taskIndex++
this.taskId = this.tasks[this.taskIndex].taskId
}
Expand Down Expand Up @@ -170,6 +170,7 @@ export default defineComponent({
</v-container>
<option-buttons
v-if="taskId"
:disabled="!isImageLoaded"
:options="options"
:result="results[taskId]"
:taskId="taskId"
Expand Down
5 changes: 5 additions & 0 deletions src/components/OptionButtons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export interface Option {
export default defineComponent({
props: {
disabled: {
type: Boolean,
default: false,
},
options: {
type: Array as PropType<Option[]>,
required: true,
Expand Down Expand Up @@ -86,6 +90,7 @@ export default defineComponent({
@click="handleOptionButtonClicked(option)"
v-shortkey="[option.shortkey]"
@shortkey="handleOptionButtonClicked(option)"
:disabled="disabled"
:title="[option.title, option.description].filter(Boolean).join(': ')"
:text="'(' + option.shortkey + ') ' + option.title"
:key="optionIndex"
Expand Down
182 changes: 182 additions & 0 deletions src/components/StreetProject.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<script lang="ts">
import createInformationPages from '@/utils/createInformationPages'
import StreetProjectTask from './StreetProjectTask.vue'
import OptionButtons from './OptionButtons.vue'
import ProjectHeader from './ProjectHeader.vue'
import ProjectInfo from './ProjectInfo.vue'
import TaskProgress from '@/components/TaskProgress.vue'
import StreetProjectInstructions from './StreetProjectInstructions.vue'
import { defineComponent } from 'vue'
export default defineComponent({
components: {
streetProjectInstructions: StreetProjectInstructions,
streetProjectTask: StreetProjectTask,
optionButtons: OptionButtons,
projectHeader: ProjectHeader,
projectInfo: ProjectInfo,
taskProgress: TaskProgress,
},
props: {
group: {
type: Object,
required: true,
},
first: {
type: Boolean,
default: false,
},
options: {
type: Array,
default() {
return [
{
mdiIcon: 'mdi-check-bold',
iconColor: '#bbcb7d',
shortkey: 1,
title: 'Yes',
value: 1,
},
{
mdiIcon: 'mdi-close-thick',
iconColor: '#fd5054',
shortkey: 2,
title: 'No',
value: 0,
},
{
mdiIcon: 'mdi-minus-thick',
iconColor: '#adadad',
title: 'Not sure',
shortkey: 3,
value: 2,
},
]
},
},
project: {
type: Object,
required: true,
},
tasks: {
type: Array,
required: true,
},
tutorial: {
type: Object,
required: false,
},
},
data() {
return {
arrowKeys: true,
isLoading: true,
results: {},
startTime: null,
taskId: undefined,
taskIndex: 0,
}
},
inject: {
logMappingStarted: 'logMappingStarted',
saveResults: 'saveResults',
},
computed: {
instructionMessage() {
const message = this.project?.lookFor
return message
},
},
methods: {
addResult(value) {
this.results[this.taskId] = value
},
back() {
if (!this.taskIndex <= 0) {
this.taskIndex--
this.taskId = this.tasks[this.taskIndex].taskId
}
},
createInformationPages,
// fallback information pages for street projects tbd
createFallbackInformationPages() {
return undefined
},
forward() {
if (!this.isLoading && this.isAnswered() && this.taskIndex + 1 < this.tasks.length) {
this.taskIndex++
this.taskId = this.tasks[this.taskIndex].taskId
}
},
isAnswered() {
const result = this.results[this.taskId]
const defined = result !== undefined
return defined
},
},
created() {
this.startTime = new Date().toISOString()
this.taskId = this.tasks[this.taskIndex].taskId
this.$emit('created')
this.logMappingStarted(this.project.projectType)
},
})
</script>

<template>
<project-header :instructionMessage="instructionMessage" :title="project?.projectTopic">
<project-info
:first="first"
:informationPages="createInformationPages(tutorial, project, createFallbackInformationPages)"
:manualUrl="project?.manualUrl"
@toggle-dialog="arrowKeys = !arrowKeys"
>
<template #instructions>
<street-project-instructions :instructionMessage="instructionMessage" :options="options" />
</template>
</project-info>
</project-header>
<street-project-task :taskId="taskId" @dataloading="(e) => (isLoading = e.loading)" />
<option-buttons
v-if="taskId"
:disabled="isLoading"
:options="options"
:result="results[taskId]"
:taskId="taskId"
@addResult="addResult"
/>
<v-toolbar color="white" extension-height="20" density="compact" extended>
<v-spacer />
<v-btn
:title="$t('streetProject.moveLeft')"
icon="mdi-chevron-left"
color="secondary"
:disabled="taskIndex <= 0"
@click="back"
v-shortkey.once="[arrowKeys ? 'arrowleft' : '']"
@shortkey="back"
/>
<v-btn
:title="$t('projectView.saveResults')"
icon="mdi-content-save"
color="primary"
:disabled="Object.keys(results).length < tasks.length"
@click="saveResults(results, startTime)"
/>
<v-btn
:title="$t('streetProject.moveRight')"
icon="mdi-chevron-right"
color="secondary"
:disabled="isLoading || !isAnswered() || taskIndex + 1 === tasks.length"
@click="forward"
v-shortkey.once="[arrowKeys ? 'arrowright' : '']"
@shortkey="forward"
/>
<v-spacer />
<template #extension>
<task-progress :progress="taskIndex + isAnswered()" :total="tasks.length" />
</template>
</v-toolbar>
</template>

<style scoped></style>
67 changes: 67 additions & 0 deletions src/components/StreetProjectInstructions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
instructionMessage: {
type: String,
required: true,
},
attribution: {
type: String,
require: false,
},
options: {
type: Array,
required: true,
},
},
})
</script>

<template>
<v-card-text>
<div class="text-h6">{{ $t('projectInstructions.classifyTitle') }}</div>
<div class="text-p">
{{ instructionMessage }} {{ $t('projectInstructions.classifyInstruction') }}.
</div>

<v-row v-for="(option, optionIndex) in options" :key="optionIndex" align="center" dense>
<v-col cols="auto" class="mr-4">
<v-btn
class="mx-2 text-caption"
width="6rem"
:text="'(' + option.shortkey + ') ' + option.title"
:color="option.iconColor"
:prepend-icon="option.mdiIcon"
variant="plain"
stacked
/>
</v-col>
<v-col>{{ [option.title, option.description].filter(Boolean).join(': ') }}</v-col>
</v-row>

<div class="text-h6 mt-10">{{ $t('projectInstructions.useButtonsToNavigate') }}</div>
<div class="text-p mt-2">
<v-row class="align-center" dense>
<v-col cols="auto" class="mr-4">
<v-btn icon="mdi-chevron-left" color="secondary" class="mr-2" variant="text" />
<v-btn icon="mdi-chevron-right" color="secondary" variant="text" />
</v-col>
<v-col>{{ $t('projectInstructions.move') }}</v-col>
</v-row>
</div>

<div class="text-h6 mt-10">{{ $t('projectInstructions.saveYourAnswers') }}</div>
<div class="text-p mt-2">
<v-row class="align-center" dense>
<v-col cols="auto" class="mr-4">
<v-btn icon="mdi-content-save" color="primary" variant="text" />
</v-col>
<v-col>{{ $t('projectInstructions.seenAll') }}</v-col>
</v-row>
</div>
</v-card-text>
</template>

<style scoped></style>
57 changes: 57 additions & 0 deletions src/components/StreetProjectTask.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script lang="ts">
import { Viewer } from 'mapillary-js'
import 'mapillary-js/dist/mapillary.css'
import { defineComponent } from 'vue'
export default defineComponent({
props: {
taskId: {
type: String,
required: true,
},
},
data() {
return {
viewer: null,
}
},
watch: {
taskId(newTaskId) {
this.viewer.moveTo(newTaskId).then(() => this.resetView())
},
},
methods: {
initialiseViewer(imageId) {
this.viewer = new Viewer({
accessToken: import.meta.env.VITE_MAPILLARY_API_KEY,
component: { cover: false },
container: 'mapillary',
imageId: imageId,
renderMode: 0, // Letterbox
})
this.viewer.deactivateComponent('direction')
this.viewer.deactivateComponent('sequence')
this.viewer.deactivateComponent('keyboard')
this.viewer.on('dataloading', (e) => this.$emit('dataloading', e))
},
resetView() {
this.viewer.setCenter([0.5, 0.5])
this.viewer.setZoom(0)
},
},
mounted() {
this.initialiseViewer(this.taskId)
},
})
</script>

<template>
<v-container
id="mapillary"
class="ma-0 pa-0"
style="position: relative; height: calc(100vh - 375px)"
/>
</template>

<style scoped></style>
Loading

0 comments on commit 44e454c

Please sign in to comment.