Skip to content

Commit

Permalink
Merge pull request #159 from Geoportail-Luxembourg/GSLUX-743-modify-c…
Browse files Browse the repository at this point in the history
…ircle

GSLUX-743: Modify circle geometries
  • Loading branch information
tkohr authored Oct 18, 2024
2 parents 24874eb + 609fa10 commit 606bcab
Show file tree
Hide file tree
Showing 20 changed files with 330 additions and 129 deletions.
18 changes: 18 additions & 0 deletions cypress/e2e/draw/draw-feat-circle.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ describe('Draw "Circle"', () => {
testFeatItemMeasurements()
})

it('updates length, area and radius measurements when editing geometry on map', () => {
cy.get('*[data-cy="featItemLength"]').should('contain.text', '346.59 km')
cy.get('*[data-cy="featItemArea"]').should('contain.text', '9559.11 km²')
cy.get('*[data-cy="featItemInputRadius"]').should(
'have.value',
'55161.21'
)
cy.dragVertexOnMap(200, 200, 300, 300)
cy.get('*[data-cy="featItemLength"]').should('contain.text', '693.17 km')
cy.get('*[data-cy="featItemArea"]').should('contain.text', '38235.40 km²')
})

it('updates length and area measurements when editing radius in panel', () => {
cy.get('*[data-cy="featItemInputRadius"]').type('{selectall}1000{enter}')
cy.get('*[data-cy="featItemLength"]').should('contain.text', '6.28 km')
cy.get('*[data-cy="featItemArea"]').should('contain.text', '3.13 km²')
})

it('displays the possible actions for the feature', () => {
testFeatItem()
})
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/draw/draw-feat-line.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ describe('Draw "Line"', () => {
})

it('updates length measurement when editing geometry', () => {
cy.get('*[data-cy="featItemLength"]').should('contain.text', '55.4 km')
cy.get('*[data-cy="featItemLength"]').should('contain.text', '55.36 km')
cy.dragVertexOnMap(200, 200, 300, 300)
cy.get('*[data-cy="featItemLength"]').should('contain.text', '111 km')
cy.get('*[data-cy="featItemLength"]').should('contain.text', '111.14 km')
})

it('displays the possible actions for the feature', () => {
Expand Down
8 changes: 4 additions & 4 deletions cypress/e2e/draw/draw-feat-polygon.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ describe('Draw "Polygon"', () => {
})

it('updates length and area measurements when editing geometry', () => {
cy.get('*[data-cy="featItemLength"]').should('contain.text', '134 km')
cy.get('*[data-cy="featItemArea"]').should('contain.text', '766 km²')
cy.get('*[data-cy="featItemLength"]').should('contain.text', '133.81 km')
cy.get('*[data-cy="featItemArea"]').should('contain.text', '766.33 km²')
cy.dragVertexOnMap(200, 200, 300, 300)
cy.get('*[data-cy="featItemLength"]').should('contain.text', '238 km')
cy.get('*[data-cy="featItemArea"]').should('contain.text', '1530 km²')
cy.get('*[data-cy="featItemLength"]').should('contain.text', '238.47 km')
cy.get('*[data-cy="featItemArea"]').should('contain.text', '1532.65 km²')
})

it('displays the possible actions for the feature', () => {
Expand Down
6 changes: 4 additions & 2 deletions src/bundle/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ import { clearLayersCache } from '@/stores/layers.cache'
import i18next, { InitOptions } from 'i18next'
import backend from 'i18next-http-backend'
import I18NextVue from 'i18next-vue'
import formatDistanceDirective from '@/directives/format-distance.directive'
import formatLengthDirective from '@/directives/format-length.directive'
import formatAreaDirective from '@/directives/format-area.directive'

import App from '../App.vue'

Expand Down Expand Up @@ -79,7 +80,8 @@ export default function useLuxLib(options: LuxLibOptions) {
app.use(createPinia())
app.use(I18NextVue, { i18next })
app.use(VueDOMPurifyHTML)
app.use(formatDistanceDirective)
app.use(formatLengthDirective)
app.use(formatAreaDirective)

const createElementInstance = (component = {}, app = null) => {
return defineCustomElement(
Expand Down
91 changes: 63 additions & 28 deletions src/components/draw/feature-measurements.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import { useTranslation } from 'i18next-vue'
import { DrawnFeature } from '@/services/draw/drawn-feature'
import FeatureMeasurementsProfile from './feature-measurements-profile.vue'
import {
getFormattedArea,
getFormattedLength,
getArea,
getCircleArea,
getCircleLength,
getCircleRadius,
getLength,
} from '@/services/common/measurement.utils'
import { Geometry, Point, Polygon } from 'ol/geom'
import { Circle, Geometry, Point, Polygon } from 'ol/geom'
import { Projection } from 'ol/proj'
import useMap from '@/composables/map/map.composable'
import {
getDebouncedElevation,
getElevation,
} from './feature-measurements-helper'
import useEdit from '@/composables/draw/edit.composable'
defineProps<{
isEditingFeature?: boolean
Expand All @@ -27,21 +31,32 @@ const feature = ref<DrawnFeature | undefined>(inject('feature'))
const featureType = ref<string>(feature.value?.featureType || '')
const featureGeometry = ref<Geometry | undefined>(feature.value?.getGeometry())
const featLength = computed(() =>
featureGeometry.value &&
['drawnLine', 'drawnCircle', 'drawnPolygon'].includes(featureType.value)
? getFormattedLength(featureGeometry.value as Geometry, mapProjection)
: undefined
)
const featArea = computed(() =>
featureGeometry.value &&
['drawnPolygon', 'drawnCircle'].includes(featureType.value)
? getFormattedArea(featureGeometry.value as Polygon)
const featLength = computed(() => {
if (featureGeometry.value) {
if (['drawnLine', 'drawnPolygon'].includes(featureType.value)) {
return getLength(featureGeometry.value as Geometry, mapProjection)
} else if (featureType.value === 'drawnCircle') {
return getCircleLength(featureGeometry.value as Circle, mapProjection)
}
}
return undefined
})
const featArea = computed(() => {
if (featureGeometry.value) {
if (featureType.value === 'drawnPolygon') {
return getArea(featureGeometry.value as Polygon)
} else if (featureType.value === 'drawnCircle') {
return getCircleArea(featureGeometry.value as Circle, mapProjection)
}
}
return undefined
})
const featRadius = computed(() =>
featureGeometry.value && featureType.value === 'drawnCircle'
? getCircleRadius(featureGeometry.value as Circle, mapProjection)
: undefined
)
// TODO: implement once circle is kept as a circle geometry,
// also adapt length and area calculation for circle then
const featRadius = feature.value?.id + ' [TODO featRayon]' // TODO
const inputRadius = ref<number>(featRadius.value || 0)
const featElevation = ref<number | undefined>()
Expand All @@ -58,21 +73,35 @@ watchEffect(async () => {
}
})
function onClickValidateRadius() {
alert('TODO: validate /save radius')
watchEffect(() => {
inputRadius.value =
featureType.value === 'drawnCircle'
? parseFloat(
getCircleRadius(
featureGeometry.value as Circle,
mapProjection
).toFixed(2)
)
: 0
})
function onClickValidateRadius(radius: number) {
if (feature.value) {
useEdit().setRadius(feature.value as DrawnFeature, Number(radius))
}
}
</script>

<template>
<div class="lux-drawing-item-measurements">
<!-- Feature length, for LineString, Circle, Polygon -->
<div data-cy="featItemLength" v-if="featLength">
<span>{{ t('Length:') }}</span> <span>{{ featLength }}</span>
<span>{{ t('Length:') }}</span> <span v-format-length="featLength"></span>
</div>

<!-- Feature area, for Circle, Polygon -->
<div data-cy="featItemArea" v-if="featArea">
<span>{{ t('Area:') }}</span> <span>{{ featArea }}</span>
<span>{{ t('Area:') }}</span> <span v-format-area="featArea"></span>
</div>

<!-- Feature radius, for Circle -->
Expand All @@ -81,12 +110,21 @@ function onClickValidateRadius() {
v-if="featureType === 'drawnCircle'"
class="flex items-center"
>
<span>{{ t('Rayon:') }}</span>
<span v-if="!isEditingFeature">{{ featRadius }}</span>
<span>{{ t('Rayon:') }} </span>
<span v-if="!isEditingFeature" v-format-length="featRadius"></span>
<!-- Radius is editable when edition mode is on -->
<div v-else class="flex">
<input class="form-control block" type="text" />
<button class="lux-btn-primary" @click="onClickValidateRadius">
<input
data-cy="featItemInputRadius"
class="form-control block bg-secondary text-white border !border-gray-300"
type="number"
v-model="inputRadius"
@keyup.enter="onClickValidateRadius(inputRadius)"
/>
<button
class="lux-btn-primary"
@click="onClickValidateRadius(inputRadius)"
>
{{ t('Valider') }}
</button>
</div>
Expand All @@ -95,10 +133,7 @@ function onClickValidateRadius() {
<!-- Feature elevation, for Point -->
<div v-if="featureType === 'drawnPoint'">
<span>{{ t('Elevation') }}: </span>
<span
data-cy="featItemElevation"
v-format-distance="featElevation"
></span>
<span data-cy="featItemElevation" v-format-length="featElevation"></span>
</div>

<!-- Feature elevation profile LineString -->
Expand Down
12 changes: 5 additions & 7 deletions src/composables/draw/draw-tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { unByKey } from 'ol/Observable'
import { Circle, Geometry, LineString, Polygon } from 'ol/geom'
import OlMap from 'ol/Map'
import { DrawEvent } from 'ol/interaction/Draw'
import {
getFormattedLength,
getFormattedArea,
} from '@/services/common/measurement.utils'
import { getLength, getArea } from '@/services/common/measurement.utils'
import { formatLength, formatArea } from '@/services/common/formatting.utils'

class DrawTooltip {
private measureTooltipElement: HTMLElement | null = null
Expand Down Expand Up @@ -69,7 +67,7 @@ class DrawTooltip {
const geom = geometry as LineString
coord = geom.getLastCoordinate()
if (coord !== null) {
output = getFormattedLength(geom, proj)
output = formatLength(getLength(geom, proj))
}
} else if (geometry.getType() === 'Polygon') {
const geom = geometry as Polygon
Expand All @@ -78,14 +76,14 @@ class DrawTooltip {
coord = geom.getInteriorPoint().getCoordinates()
}
if (coord !== null) {
output = getFormattedArea(geom)
output = formatArea(getArea(geom))
}
} else if (geometry.getType() === 'Circle') {
const geom = geometry as Circle
coord = geom.getLastCoordinate()
const center = geom.getCenter()
if (center !== null && coord !== null) {
output = getFormattedLength(new LineString([center, coord]), proj)
output = formatLength(getLength(new LineString([center, coord]), proj))
}
}
if (this.measureTooltipElement) {
Expand Down
58 changes: 49 additions & 9 deletions src/composables/draw/draw-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Circle, Geometry } from 'ol/geom'
import { fromCircle } from 'ol/geom/Polygon'
import { Feature } from 'ol'
import { Circle } from 'ol/geom'
import Polygon, { fromCircle } from 'ol/geom/Polygon'
import { getDistance } from 'ol/sphere'
import { toLonLat } from 'ol/proj'
import { DrawnFeature } from '@/services/draw/drawn-feature'
import { setCircleRadius } from '@/services/common/measurement.utils'
import useMap from '../map/map.composable'
import { Map } from 'ol'

// TODO 3D
// import { transform } from 'ol/proj'
Expand All @@ -9,14 +14,49 @@ import { Feature } from 'ol'
// TODO 3D
// const ARROW_MODEL_URL = import.meta.env.VITE_ARROW_MODEL_URL

function convertCircleToPolygon(
feature: Feature<Geometry>,
featureType: String
) {
/**
* Note that feature.featureType and geom?.getType() values mostly correspond to each other.
* One exception are 'drawnCircle' featureTypes that are managed as 'Polygon' geometries within the URL and during export.
* (Another exception are 'drawnLabel' featureTypes that are managed as 'Point' geometries throughout the application.)
* @param feature Feature with a circle geometry
* @returns The same feature with a polygon geometry
*/
function convertCircleFeatureToPolygon(feature: DrawnFeature): DrawnFeature {
const geom = feature.getGeometry()
if (featureType == 'drawnCircle' && geom?.getType() == 'Circle') {
if (feature.featureType === 'drawnCircle' && geom?.getType() === 'Circle') {
feature.setGeometry(fromCircle(geom as Circle, 64))
}
return feature
}

export { convertCircleToPolygon }
/**
*
* @param feature Feature with a polygon geometry
* @returns The same feature with a circle geometry
*/

function convertPolygonFeatureToCircle(feature: DrawnFeature): DrawnFeature {
const map: Map = useMap().getOlMap()
const polygon = feature.getGeometry() as Polygon
if (
feature.featureType === 'drawnCircle' &&
polygon?.getType() === 'Polygon'
) {
const extent = polygon.getExtent()
const centroid = [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2]
const coordinates = polygon.getCoordinates()[0]
let maxDistance = 0
coordinates.forEach(coord => {
const distance = getDistance(toLonLat(centroid), toLonLat(coord))
if (distance > maxDistance) {
maxDistance = distance
}
})
const circle = new Circle(centroid)
setCircleRadius(circle, maxDistance, map)
feature.setGeometry(circle as Circle)
}
return feature
}

export { convertCircleFeatureToPolygon, convertPolygonFeatureToCircle }
2 changes: 0 additions & 2 deletions src/composables/draw/drawn-features.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Feature } from 'ol'
import { Point, Circle, Geometry, LineString } from 'ol/geom'
import Polygon from 'ol/geom/Polygon'
import { useDrawStore } from '@/stores/draw.store'
import { convertCircleToPolygon } from '@/composables/draw/draw-utils'
import { useAppStore } from '@/stores/app.store'
import { screenSizeIsAtLeast } from '@/services/common/device.utils'

Expand Down Expand Up @@ -64,7 +63,6 @@ export default function useDrawnFeatures() {
featureType,
featureStyle,
})
convertCircleToPolygon(drawnFeature, featureType)

addDrawnFeature(drawnFeature)

Expand Down
14 changes: 14 additions & 0 deletions src/composables/draw/edit.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { useDrawStore } from '@/stores/draw.store'
import useMap from '../map/map.composable'
import { EditStateActive } from '@/stores/draw.store.model'
import { DEFAULT_DRAW_ZINDEX, FEATURE_LAYER_TYPE } from './draw.composable'
import { Circle } from 'ol/geom'
import { setCircleRadius } from '@/services/common/measurement.utils'

export default function useEdit() {
const { editStateActive, editingFeatureId, drawnFeatures } = storeToRefs(
Expand Down Expand Up @@ -81,4 +83,16 @@ export default function useEdit() {
updateDrawnFeature(feature as DrawnFeature)
})
}

function setRadius(feature: DrawnFeature, radius: number) {
const geometry = feature.getGeometry()
if (geometry?.getType() === 'Circle') {
setCircleRadius(geometry as Circle, radius, map)
updateDrawnFeature(feature)
}
}

return {
setRadius,
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import i18next from 'i18next'
import { formatArea } from '@/services/common/formatting.utils'
import { App } from 'vue'

export default {
install(app: App) {
app.directive('format-distance', {
app.directive('format-area', {
beforeMount(el: HTMLElement, binding: { value: number }) {
format(el, binding.value)
},
Expand All @@ -16,12 +16,6 @@ export default {

function format(el: HTMLElement, value: number): void {
let formattedText: string = ''
if (value === null) {
formattedText = i18next.t('N/A', { ns: 'client' })
} else if (value < 1000) {
formattedText = `${value.toFixed(2)} m`
} else if (value >= 1000) {
formattedText = `${(value / 1000).toFixed(2)} km`
}
formattedText = formatArea(value)
el.textContent = formattedText
}
Loading

0 comments on commit 606bcab

Please sign in to comment.