SpeciesFileGroup/taxonworks

View on GitHub
app/javascript/vue/tasks/collecting_events/new_collecting_event/components/parsed/georeferences/georeferences.vue

Summary

Maintainability
Test Coverage
<template>
  <div>
    <button
      type="button"
      class="button normal-input button-default"
      @click="showModal = !showModal"
    >
      Georeference ({{ count }})
    </button>
    <button
      v-if="!verbatimGeoreferenceAlreadyCreated"
      type="button"
      class="button normal-input button-submit margin-small-left"
      :disabled="!verbatimLat && !verbatimLat"
      @click="createVerbatimShape"
    >
      Create georeference from verbatim
    </button>
    <template v-if="verbatimGeoreferenceAlreadyCreated">
      <span
        >Lat: {{ georeferenceVerbatimLatitude }}, Long:
        {{ georeferenceVerbatimLongitude
        }}<span v-if="georeferenceVerbatimRadiusError"
          >, Radius error: {{ georeferenceVerbatimRadiusError }}</span
        ></span
      >
    </template>
    <modal-component
      @close="showModal = false"
      :container-style="{
        width: '80vw',
        maxHeight: '80vh',
        overflowY: 'scroll'
      }"
      v-if="showModal"
    >
      <template #header>
        <h3>Georeferences</h3>
      </template>
      <template #body>
        <div style="overflow-y: scroll">
          <div
            class="horizontal-left-content margin-medium-top margin-medium-bottom"
          >
            <wkt-component
              @create="addToQueue"
              class="margin-small-right"
            />
            <manually-component
              class="margin-small-right"
              @create="addGeoreference($event, GEOREFERENCE_POINT)"
            />
            <geolocate-component
              :disabled="!collectingEvent.id"
              class="margin-small-right"
              @create="addToQueue"
            />
            <button
              type="button"
              v-if="verbatimLat && verbatimLng"
              :disabled="verbatimGeoreferenceAlreadyCreated"
              @click="createVerbatimShape"
              class="button normal-input button-submit"
            >
              Create georeference from verbatim
            </button>
          </div>
          <div
            :style="{
              height: height,
              width: width
            }"
          >
            <spinner-component
              v-if="showSpinner"
              legend="Saving..."
            />
            <map-component
              ref="leaflet"
              v-if="show"
              :height="height"
              :width="width"
              :geojson="mapGeoreferences"
              :zoom="zoom"
              fit-bounds
              resize
              :draw-controls="true"
              :draw-polyline="false"
              :cut-polygon="false"
              :removal-mode="false"
              tooltips
              actions
              @geoJsonLayersEdited="updateGeoreference($event)"
              @geoJsonLayerCreated="addGeoreference($event)"
            />
          </div>
          <div class="margin-medium-top">
            <b>Georeference date</b>
            <date-component
              v-model:day="date.day_georeferenced"
              v-model:month="date.month_georeferenced"
              v-model:year="date.year_georeferenced"
            />
          </div>
          <div
            class="horizontal-left-content margin-medium-top margin-medium-bottom"
          >
            <wkt-component
              @create="addToQueue"
              class="margin-small-right"
            />
            <manually-component
              class="margin-small-right"
              @create="addGeoreference($event, GEOREFERENCE_POINT)"
            />
            <geolocate-component
              class="margin-small-right"
              @create="addToQueue"
            />
            <button
              type="button"
              v-if="verbatimLat && verbatimLng"
              :disabled="verbatimGeoreferenceAlreadyCreated"
              @click="createVerbatimShape"
              class="button normal-input button-submit"
            >
              Create georeference from verbatim
            </button>
          </div>
          <display-list
            :list="collectingEventId ? georeferences : queueGeoreferences"
            @delete="removeGeoreference"
            @update="updateRadius"
            @dateChanged="addToQueue"
            label="object_tag"
          />
        </div>
      </template>
    </modal-component>
  </div>
</template>

<script>
import MapComponent from '@/components/georeferences/map'
import SpinnerComponent from '@/components/ui/VSpinner'
import DisplayList from './list'
import convertDMS from '@/helpers/parseDMS.js'
import ManuallyComponent from '@/components/georeferences/manuallyComponent'
import GeolocateComponent from './geolocate'
import ModalComponent from '@/components/ui/Modal'
import extendCE from '../../mixins/extendCE'
import WktComponent from './wkt'
import DateComponent from '@/components/ui/Date/DateFields.vue'
import { Georeference } from '@/routes/endpoints'
import { truncateDecimal } from '@/helpers/math.js'
import { GetterNames } from '../../../store/getters/getters'
import { MutationNames } from '../../../store/mutations/mutations'
import { ActionNames } from '../../../store/actions/actions'
import {
  GEOREFERENCE_GEOLOCATE,
  GEOREFERENCE_EXIF,
  GEOREFERENCE_POINT,
  GEOREFERENCE_VERBATIM,
  GEOREFERENCE_WKT,
  GEOREFERENCE_LEAFLET
} from '@/constants/index.js'

export default {
  mixins: [extendCE],

  components: {
    MapComponent,
    SpinnerComponent,
    DisplayList,
    ManuallyComponent,
    GeolocateComponent,
    ModalComponent,
    WktComponent,
    DateComponent
  },

  emits: ['onGeoreferences'],

  props: {
    height: {
      type: String,
      default: '500px'
    },

    width: {
      type: String,
      default: 'auto'
    },

    geolocationUncertainty: {
      type: [String, Number],
      default: undefined
    },

    zoom: {
      type: Number,
      default: 1
    },

    show: {
      type: Boolean,
      default: true
    }
  },

  computed: {
    georeferenceVerbatimLatitude() {
      return this.verbatimGeoreferenceAlreadyCreated
        ? truncateDecimal(
            this.verbatimGeoreferenceAlreadyCreated.geo_json
              ? this.verbatimGeoreferenceAlreadyCreated.geo_json.geometry
                  .coordinates[1]
              : JSON.parse(
                  this.verbatimGeoreferenceAlreadyCreated
                    .geographic_item_attributes.shape
                ).geometry.coordinates[1],
            6
          )
        : undefined
    },
    georeferenceVerbatimLongitude() {
      return this.verbatimGeoreferenceAlreadyCreated
        ? truncateDecimal(
            this.verbatimGeoreferenceAlreadyCreated.geo_json
              ? this.verbatimGeoreferenceAlreadyCreated.geo_json.geometry
                  .coordinates[0]
              : JSON.parse(
                  this.verbatimGeoreferenceAlreadyCreated
                    .geographic_item_attributes.shape
                ).geometry.coordinates[0],
            6
          )
        : undefined
    },
    georeferenceVerbatimRadiusError() {
      return this.verbatimGeoreferenceAlreadyCreated
        ? truncateDecimal(
            this.verbatimGeoreferenceAlreadyCreated.geo_json
              ? this.verbatimGeoreferenceAlreadyCreated.geo_json.properties
                  .radius
              : JSON.parse(
                  this.verbatimGeoreferenceAlreadyCreated
                    .geographic_item_attributes.shape
                ).error_radius,
            6
          )
        : undefined
    },
    verbatimGeoreferenceAlreadyCreated() {
      return []
        .concat(this.georeferences, this.queueGeoreferences)
        .find(
          (item) =>
            item.type === GEOREFERENCE_VERBATIM ||
            item.type === GEOREFERENCE_EXIF
        )
    },
    mapGeoreferences() {
      return [].concat(
        this.shapes.features,
        this.queueGeoreferences
          .filter(
            (item) =>
              item.type !== GEOREFERENCE_WKT &&
              item.type !== GEOREFERENCE_GEOLOCATE &&
              item?.geographic_item_attributes?.shape
          )
          .map((item) => JSON.parse(item?.geographic_item_attributes?.shape))
      )
    },
    geojson() {
      return this.collectingEventId
        ? this.shapes.features
        : this.queueGeoreferences
    },
    count() {
      return this.geojson.filter((item) => !item?.properties?.geographic_area)
        .length
    },
    verbatimLat() {
      return this.collectingEvent.verbatim_latitude
    },
    verbatimLng() {
      return this.collectingEvent.verbatim_longitude
    },
    geographicArea() {
      return this.$store.getters[GetterNames.GetGeographicArea]?.shape
    },
    GEOREFERENCE_POINT() {
      return GEOREFERENCE_POINT
    },
    georeferences: {
      get() {
        return this.$store.getters[GetterNames.GetGeoreferences]
      },
      set(value) {
        this.$store.commit(MutationNames.SetGeoreferences, value)
      }
    },
    queueGeoreferences: {
      get() {
        return this.$store.getters[GetterNames.GetQueueGeoreferences]
      },
      set(value) {
        this.$store.commit(MutationNames.SetQueueGeoreferences, value)
      }
    }
  },

  data() {
    return {
      isProcessing: false,
      showSpinner: false,
      selectedGeoreference: undefined,
      shapes: {
        type: 'FeatureCollection',
        features: []
      },
      date: {
        year_georeferenced: undefined,
        month_georeferenced: undefined,
        day_georeferenced: undefined
      },
      showModal: false
    }
  },

  watch: {
    georeferences: {
      handler() {
        this.populateShapes()
      },
      deep: true
    },

    geographicArea: {
      handler() {
        this.populateShapes()
      },
      deep: true
    }
  },

  methods: {
    updateRadius(geo) {
      const index = geo.id
        ? this.georeferences.findIndex((item) => item.id === geo.id)
        : this.queueGeoreferences.findIndex((item) => item.tmpId === geo.tmpId)

      if (geo.id) {
        this.georeferences[index].error_radius = geo.error_radius
        this.georeferences[index].error_geographic_item_id =
          geo.geographic_item_id
        this.queueGeoreferences.push(this.georeferences[index])
      } else {
        this.queueGeoreferences[index].error_radius = geo.error_radius
      }
    },

    addGeoreference(shape, type = GEOREFERENCE_LEAFLET) {
      this.addToQueue({
        tmpId: Math.random().toString(36).substr(2, 5),
        geographic_item_attributes: { shape: JSON.stringify(shape) },
        error_radius: shape.properties?.radius,
        type,
        ...this.date
      })
    },

    updateGeoreference(shape, type = GEOREFERENCE_LEAFLET) {
      this.addToQueue({
        id: shape.properties.georeference.id,
        error_radius: shape.properties?.radius,
        geographic_item_attributes: { shape: JSON.stringify(shape) },
        collecting_event_id: this.collectingEventId,
        type
      })
    },

    populateShapes() {
      this.shapes.features = []
      if (this.geographicArea) {
        this.shapes.features.unshift(this.geographicArea)
      }
      this.georeferences.forEach((geo) => {
        if (geo.error_radius != null) {
          geo.geo_json.properties.radius = geo.error_radius
        }
        this.shapes.features.push(geo.geo_json)
      })
    },

    removeGeoreference(geo) {
      const index = geo.id
        ? this.georeferences.findIndex((item) => item.id === geo.id)
        : this.queueGeoreferences.findIndex((item) => item.tmpId === geo.tmpId)
      if (geo.id) {
        Georeference.destroy(geo.id).then(() => {
          this.georeferences.splice(index, 1)
          this.$emit('onGeoreferences', this.georeferences)
          this.populateShapes()
        })
      } else {
        this.queueGeoreferences.splice(index, 1)
      }
    },

    createVerbatimShape() {
      const shape = {
        type: 'Feature',
        properties: {},
        geometry: {
          type: 'Point',
          coordinates: [
            convertDMS(this.verbatimLng),
            convertDMS(this.verbatimLat)
          ]
        }
      }

      this.addToQueue({
        geographic_item_attributes: { shape: JSON.stringify(shape) },
        collecting_event_id: this.collectingEventId,
        type: GEOREFERENCE_VERBATIM,
        error_radius: this.collectingEvent.verbatim_geolocation_uncertainty
      })
    },

    addToQueue(data) {
      this.$store.commit(MutationNames.AddGeoreferenceToQueue, data)
      this.$store.dispatch(ActionNames.ProcessGeoreferenceQueue)
    }
  }
}
</script>