Skip to content

Commit

Permalink
geosolutions-it#6537: Measurement panel improvement (III) (geosolutio…
Browse files Browse the repository at this point in the history
…ns-it#6555)

* geosolutions-it#6537: Measurement panel improvement (III)

* Remove 0 label

* Minor annotation fix
  • Loading branch information
dsuren1 authored Feb 22, 2021
1 parent 6c3abad commit 5724a7e
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 36 deletions.
3 changes: 0 additions & 3 deletions web/client/components/I18N/IntlNumberFormControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ class IntlNumberFormControl extends React.Component {
{...formProps}
{...value !== undefined ? {value: this.format(value) } : {defaultValue: this.format(defaultValue)}}
format={this.format}
onKeyUp={ev=>{
ev.target.setSelectionRange(-1, -1);
}}
onChange={(val) => {
val === null ? this.props.onChange("") : this.props.onChange(val.toString());
}}
Expand Down
103 changes: 87 additions & 16 deletions web/client/components/map/openlayers/MeasurementSupport.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ export default class MeasurementSupport extends React.Component {
startEndPoint: {
startPointOptions: {
radius: 3,
fillColor: "green"
fillColor: "green",
applyToPolygon: true
},
endPointOptions: {
radius: 3,
fillColor: "red"
fillColor: "red",
applyToPolygon: true
}
},
updateOnMouseMove: false
Expand Down Expand Up @@ -163,8 +165,8 @@ export default class MeasurementSupport extends React.Component {
/**
* Update the draw interaction when feature in edit
*/
updateInteraction = (props) => {
const disableInteraction = props.measurement.features.some(ft=> get(ft, 'properties.disabled'));
updateInteraction = (props, features) => {
const disableInteraction = features.some(ft=> get(ft, 'properties.disabled'));
if (disableInteraction) {
// Disable interaction (To allow add coordinate points by click on map)
this.removeDrawInteraction();
Expand All @@ -175,29 +177,89 @@ export default class MeasurementSupport extends React.Component {
}
}

/**
* Create LineString geometry for Polygon to display the feature
* when polygon is not fully formed
*/
convertPolyToLineFeature = (coordinates = []) => {
let reprojectedCoords = [];
const firstIndexValid = validateCoord(coordinates[0]);
if (firstIndexValid && validateCoord(coordinates[1])) {
reprojectedCoords = this.reprojectedCoordinatesFrom4326([coordinates[0], coordinates[1]]);
} else if (firstIndexValid) {
reprojectedCoords = this.reprojectedCoordinatesFrom4326([coordinates[0]]);
}
if (reprojectedCoords.length) {
return new LineString(reprojectedCoords);
}
return null;
}

/**
* Modify feature based on the geometries validity
*/
modifyFeatures = (props) => {
let {currentFeature, features} = props.measurement || {};
let coordinates = features[currentFeature]?.geometry?.coordinates || [];
let type = features[currentFeature]?.geometry?.type || '';
if (type === "Polygon") {
coordinates = coordinates?.[0] || [];
}
const coordinatesLength = coordinates.length;
const hasInvalidCoords = coordinates.some(c=> !validateCoord(c));
let geometryObj;
// Modify only when LineString or Polygon is not fully formed
if (!hasInvalidCoords && type === 'LineString' && coordinatesLength < 2) {
const reprojectedCoords = this.reprojectedCoordinatesFrom4326(coordinates);
geometryObj = new LineString(reprojectedCoords);
features[currentFeature] = set("properties.disabled", true, set("geometry.coordinates", [...coordinates, ["", ""]], features[currentFeature]));
} else if (!hasInvalidCoords && type === "Polygon" && coordinatesLength <= 4
&& isEqual(coordinates[coordinatesLength - 1], coordinates[coordinatesLength - 2])) {
coordinates.splice(2, 0, ["", ""]);
coordinates.pop();
features[currentFeature] = set("properties.disabled", true, set("geometry.coordinates", [coordinates], features[currentFeature]));
}
if (type === "Polygon" && coordinatesLength <= 4) {
geometryObj = this.convertPolyToLineFeature(coordinates);
}
if (!hasInvalidCoords && isEqual(coordinates[coordinatesLength - 1], coordinates[coordinatesLength - 2])) {
features[currentFeature]?.geometry?.coordinates?.[0]?.pop(); // Pop last duplicate coordinate of a Polygon to prevent extra rows in Measure panel
}
let currentOlFt;
if (geometryObj) {
this.source.clear();
currentOlFt = new Feature({geometry: geometryObj}); // Add feature with new geometry
}
return [features, currentOlFt];
}

updateFeatures = (props) => {
const [features, currentOlFt] = this.modifyFeatures(props) || [];
const oldFeatures = this.source.getFeatures();

this.removeMeasureTooltips();
this.removeSegmentLengthOverlays();
this.source.clear();
this.textLabels = [];
this.segmentLengths = [];
let indexOfFeatureInEdit = null;
this.updateInteraction(props);
const results = props.measurement.features.map((feature, index) => {
this.updateInteraction(props, features);
const results = features.map((feature, index) => {
if (get(feature, 'properties.disabled')) {
indexOfFeatureInEdit = index;
return [feature, oldFeatures && oldFeatures[index] && oldFeatures[index].getGeometry()];
return [feature, oldFeatures && oldFeatures[index] && oldFeatures[index].getGeometry() || currentOlFt?.getGeometry()];
}

const geomType = feature.geometry.type;
const featureValues = get(feature, 'properties.values', []);
const isBearing = (featureValues[0] || {}).type === 'bearing' ||
(!(featureValues[0] || {}).type && props.measurement.bearingMeasureEnabled);
const coords = geomType === 'Polygon' ? feature.geometry.coordinates[0] : feature.geometry.coordinates;
const reprojectedCoords = this.reprojectedCoordinatesFrom4326(coords);
const geometryObj = geomType === 'Polygon' ? new Polygon([reprojectedCoords]) : new LineString(reprojectedCoords);
let newCoords = coords;
if (geomType === 'LineString' && coords.length >= 2 && !isBearing) {
newCoords = transformLineToArcs(coords); // Maintain the arc when updating features
}
const newReprojectedCoords = this.reprojectedCoordinatesFrom4326(newCoords);
const geometryObj = geomType === 'Polygon' ? new Polygon([newReprojectedCoords]) : new LineString(newReprojectedCoords);

const getMeasureValue = {
'Point': () => coords,
Expand All @@ -217,6 +279,7 @@ export default class MeasurementSupport extends React.Component {
'Polygon': () => this.formatAreaValue(this.getArea(geometryObj), props.uom)
};

const reprojectedCoords = this.reprojectedCoordinatesFrom4326(coords);
// recalculate segments
if (!isBearing) {
for (let i = 0; i < coords.length - 1; ++i) {
Expand All @@ -227,10 +290,22 @@ export default class MeasurementSupport extends React.Component {
const bearingText = this.props.measurement && this.props.measurement.showLengthAndBearingLabel && " | " + getFormattedBearingValue(segmentLengthBearing, this.props.measurement.trueBearing) || "";
const overlayText = this.formatLengthValue(segmentLengthDistance, props.uom, isBearing) + bearingText;
last(this.segmentOverlayElements).innerHTML = overlayText;
last(this.segmentOverlays).setPosition(midpoint(reprojectedCoords[i], reprojectedCoords[i + 1], true));
let textLabelPosition = midpoint(coords[i], coords[i + 1], true);
let segmentOverlayPosition = midpoint(reprojectedCoords[i], reprojectedCoords[i + 1], true);

// Generate correct textLabels and update segment overlays
if (geomType === 'LineString') {
const middlePoint = newCoords[100 * i + 50];
if (middlePoint) {
textLabelPosition = middlePoint;
segmentOverlayPosition = pointObjectToArray(reproject(middlePoint, 'EPSG:4326', getProjectionCode(this.props.map)));
}
}

last(this.segmentOverlays).setPosition(segmentOverlayPosition);
this.textLabels[this.segmentOverlays.length - 1] = {
text: overlayText,
position: midpoint(coords[i], coords[i + 1], true)
position: textLabelPosition
};
this.segmentLengths[this.segmentOverlays.length - 1] = {
value: segmentLengthDistance,
Expand Down Expand Up @@ -312,12 +387,8 @@ export default class MeasurementSupport extends React.Component {
} else {
newFeature.geometry.textLabels = tempTextLabels.splice(0, currentCoordinateLength - sliceVal) || [];
}
if (isPolygon && !hasInvalidCoords && coordinates.length && isEqual(coordinates[currentCoordinateLength], coordinates[currentCoordinateLength - 1])) {
newFeature.geometry.coordinates[0].pop(); // Pop last coordinate of a Polygon to prevent extra rows in Measure panel
}
return newFeature;
});

this.props.changeGeometry(newFeatures);
this.props.setTextLabels([...this.textLabels]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import expect from 'expect';

import MeasurementSupport from '../MeasurementSupport';

import { LineString } from 'ol/geom';
import { LineString, Polygon } from 'ol/geom';
import { Map, View, Feature } from 'ol';

describe('Openlayers MeasurementSupport', () => {
Expand Down Expand Up @@ -389,15 +389,16 @@ describe('Openlayers MeasurementSupport', () => {
updatedByUI: true,
showLabel: true,
showLengthAndBearingLabel: true,
features
features,
currentFeature: 0
},
uom
});

expect(cmp.outputValues).toExist();
expect(cmp.outputValues.length).toBe(2);
expect(cmp.textLabels).toExist();
expect(cmp.textLabels.length).toBe(4);
expect(cmp.textLabels.length).toBe(3);

expect(spyOnChangeGeometry).toHaveBeenCalled();
const resultFeature = spyOnChangeGeometry.calls[0].arguments[0];
Expand All @@ -406,7 +407,7 @@ describe('Openlayers MeasurementSupport', () => {
expect(resultFeature[0].geometry).toBeTruthy();
expect(resultFeature[0].geometry.type).toBe('Polygon');
expect(resultFeature[0].geometry.coordinates[0].length).toBe(4);
expect(resultFeature[0].geometry.textLabels.length).toBe(4);
expect(resultFeature[0].geometry.textLabels.length).toBe(3);
});
it('test drawing (LineString)', () => {
const spyOnChangeGeometry = expect.spyOn(testHandlers, "changeGeometry");
Expand Down Expand Up @@ -924,4 +925,74 @@ describe('Openlayers MeasurementSupport', () => {
// Restore drawInteraction when feature is valid
expect(cmp.drawInteraction).toBeTruthy();
});

it('test modify features when geometry in edit is not fully formed', () => {
const spyOnchangeGeometry = expect.spyOn(testHandlers, 'changeGeometry');
let cmp = renderMeasurement();
cmp = renderMeasurement({
measurement: {
geomType: "LineString",
lineMeasureEnabled: true,
updatedByUI: false,
showLabel: true
},
uom
});

cmp.drawInteraction.dispatchEvent({
type: 'drawstart',
feature: new Feature({
geometry: new Polygon([[10.0, 15.0], [10.0, 15.0]])
})
});

// LineString
let ftLineString = {
type: "Feature",
geometry: {type: "LineString", coordinates: [[1, 2]], textLabels: []}
};
cmp = renderMeasurement({
measurement: {
geomType: "LineString",
lineMeasureEnabled: true,
updatedByUI: true,
features: [ftLineString],
currentFeature: 0
},
uom
});
expect(cmp.source.getFeatures().length).toBe(1);
let feature = cmp.source.getFeatures()[0];
expect(feature.getGeometry().getType()).toBe('LineString');
expect(spyOnchangeGeometry).toHaveBeenCalled();
let updatedFeatures = spyOnchangeGeometry.calls[0].arguments[0];
expect(updatedFeatures[0].geometry.coordinates).toEqual([[1, 2], ["", ""]]);
expect(updatedFeatures[0].geometry.textLabels).toEqual([{text: "0"}]);
expect(updatedFeatures[0].properties.disabled).toBe(true);

// Polygon
cmp = renderMeasurement({
measurement: {
geomType: "Polygon",
areaMeasureEnabled: true,
currentFeature: 1,
updatedByUI: true,
features: [{...ftLineString, geometry: {...ftLineString.geometry, coordinates: [[1, 2], [2, 3]]}}, {
type: "Feature",
geometry: {type: "Polygon", coordinates: [[[1, 2], ["", ""], [1, 2]]], textLabels: [{text: "0"}, {text: "0"}]},
properties: {disabled: true}
}]
},
uom
});

// Generate Linestring geometry for unformed Polygon
expect(cmp.source.getFeatures().length).toBe(2);
feature = cmp.source.getFeatures()[1];
expect(feature.getGeometry().getType()).toBe('LineString');
updatedFeatures = spyOnchangeGeometry.calls[1].arguments[0];
expect(updatedFeatures[1].geometry.coordinates).toEqual([[[1, 2], ["", ""], [1, 2]]]);
expect(updatedFeatures[1].geometry.textLabels.length).toBe(3);
expect(updatedFeatures[1].properties.disabled).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import 'react-quill/dist/quill.snow.css';

import { head, isEmpty, isFunction } from 'lodash';
import { head, isEmpty, isFunction, isUndefined } from 'lodash';
import assign from 'object-assign';
import PropTypes from 'prop-types';
import React from 'react';
Expand Down Expand Up @@ -831,7 +831,13 @@ class AnnotationsEditor extends React.Component {
if (Object.keys(errors).length === 0) {
this.props.onError({});
this.props.onSave(this.props.id, assign({}, this.props.editedFields),
this.props.editing.features, this.props.editing.style, this.props.editing.newFeature || false, this.props.editing.properties);
this.props.editing.features, this.props.editing.style, this.props.editing.newFeature || false, {
...this.props.editing.properties,
visibility: !isUndefined(this.props.editing.properties.visibility)
? this.props.editing.properties.visibility
: this.props.editing.visibility
}
);
} else {
this.props.onError(errors);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@ class CoordinatesEditor extends React.Component {
addCoordPolygon = (components) => {
if (this.props.type === "Polygon") {
const validComponents = components.filter(validateCoords);
return components.concat([validComponents.length ? validComponents[0] : {lat: "", lon: ""}]);
const coordinates = this.props.features[this.props.currentFeature]?.geometry?.coordinates?.[0] || [];
const invalidCoordinateIndex = coordinates !== undefined ? coordinates.findIndex(c=> !validateCoords({lon: c[0], lat: c[1]})) : -1;
return components.concat([validComponents.length && invalidCoordinateIndex !== 0 ? validComponents[0] : {lat: "", lon: ""}]);
}
return components;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ describe("test the AnnotationsEditor Panel", () => {
selected={null}
editing={{
properties: feature,
features: [{}]
features: [{}],
visibility: true
}}
onSave={testHandlers.onSaveHandler}
onCancelEdit={testHandlers.onCancelHandler}/>, document.getElementById("container"));
Expand All @@ -173,6 +174,8 @@ describe("test the AnnotationsEditor Panel", () => {
expect(saveButton).toExist();
TestUtils.Simulate.click(saveButton);
expect(spySave.calls.length).toEqual(1);
expect(spySave.calls[0].arguments[0]).toEqual(1);
expect(spySave.calls[0].arguments[5]).toEqual({...feature, visibility: true});
expect(spyCancel.calls.length).toEqual(0);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe("test the CoordinatesEditor Panel", () => {
{type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[10, 10], [6, 6], [6, 6]],
coordinates: [[[10, 10], [6, 6], [6, 6]]],
textLabels: [
{text: '2 m | 060°', position: [10, 10]},
{text: '3 m | 078°', position: [6, 6]},
Expand Down
2 changes: 2 additions & 0 deletions web/client/epics/__tests__/annotations-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,8 @@ describe('annotations Epics', () => {
actions.map((action) => {
switch (action.type) {
case UPDATE_NODE:
expect(action.options.features).toBeTruthy();
expect(action.options.visibility).toBe(false);
break;
case CHANGE_DRAWING_STATUS:
expect(action.owner).toBe('annotations');
Expand Down
4 changes: 3 additions & 1 deletion web/client/epics/__tests__/measurement-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ describe('measurement epics', () => {
}, state);
});
it('test setMeasureStateFromAnnotationEpic', (done) => {
const NUMBER_OF_ACTIONS = 3;
const NUMBER_OF_ACTIONS = 4;
const state = {
controls: {
measure: {
Expand All @@ -232,6 +232,8 @@ describe('measurement epics', () => {
expect(actions[2].control).toBe("annotations");
expect(actions[2].property).toBe("enabled");
expect(actions[2].value).toBe(false);
expect(actions[3].type).toBe("ANNOTATIONS:VISIBILITY");
expect(actions[3].visibility).toBe(false);
done();
}, state);
});
Expand Down
3 changes: 2 additions & 1 deletion web/client/epics/annotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ export default (viewer) => ({
properties: f.properties.id === action.id ? assign({}, f.properties, action.properties, action.fields) : f.properties,
features: f.properties.id === action.id ? featureCollection : f.features,
style: f.properties.id === action.id ? action.style : f.style
})).concat(action.newFeature ? [createNewFeature(action)] : [])
})).concat(action.newFeature ? [createNewFeature(action)] : []),
visibility: !isUndefined(action?.properties?.visibility) ? action.properties.visibility : false
})] : [
addLayer({
type: 'vector',
Expand Down
Loading

0 comments on commit 5724a7e

Please sign in to comment.