Skip to content

Commit

Permalink
2626 updates measure tool (geosolutions-it#2701)
Browse files Browse the repository at this point in the history
* Fix geosolutions-it#2626 Updated measure tool

* revert on test file

* Made some changes, added arcs on leaflet

* made length formula and showlabel configurable

* fixed default config, added documentation

* fixed reducer default
  • Loading branch information
MV88 authored Mar 6, 2018
1 parent 41fda25 commit dece305
Show file tree
Hide file tree
Showing 22 changed files with 1,023 additions and 164 deletions.
23 changes: 23 additions & 0 deletions docs/developer-guide/local-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,26 @@ Set `selectedService` value to one of the ID of the services object ("Demo CSW S
<br>Be careful to use unique IDs
<br>Future implementations will try to detect the type from the url.
<br>newService is used internally as the starting object for an empty service.

<br>
<h4> Measure Tool configuration </h4>
Inside defaultState you can set lengthFormula, showLabel, uom:
- you can customize the formula used for length calculation from "haversine" or "vincenty" (default haversine)
- show or not the measurement label on the map after drawing a measurement (default true)
- set the default uom used for measure tool (default m and sqm)
<br>For the label you can chose whatever value you want.
<br>For the unit you can chose between:
- unit length values : ft, m, km, mi, nm standing for feets, meters, kilometers, miles, nautical miles
- unit area values : sqft, sqm, sqkm, sqmi, sqnm standing for square feets, square meters, square kilometers, square miles, square nautical miles

example:<br>
```
"measurement": {
"lengthFormula": "vincenty",
"showLabel": true,
"uom": {
"length": {"unit": "m", "label": "m"},
"area": {"unit": "sqm", "label": "m²"}
}
}
```
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"@carnesen/redux-add-action-listener-enhancer": "0.0.1",
"@mapbox/togeojson": "0.16.0",
"@turf/bbox": "4.1.0",
"@turf/great-circle": "5.1.5",
"@turf/inside": "4.1.0",
"@turf/line-intersect": "4.1.0",
"@turf/polygon-to-linestring": "4.1.0",
Expand Down Expand Up @@ -122,6 +123,7 @@
"leaflet.nontiledlayer": "1.0.7",
"lodash": "4.16.6",
"moment": "2.13.0",
"node-geo-distance": "1.2.0",
"object-assign": "4.1.1",
"ogc-schemas": "2.6.1",
"openlayers": "4.6.4",
Expand Down Expand Up @@ -185,6 +187,7 @@
"turf-bbox": "3.0.10",
"turf-buffer": "3.0.10",
"turf-intersect": "3.0.10",
"turf-point": "2.0.1",
"turf-point-on-surface": "3.0.10",
"turf-union": "3.0.10",
"url": "0.10.3",
Expand Down
62 changes: 62 additions & 0 deletions web/client/actions/__tests__/measurement-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2018, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

const expect = require('expect');
const {
toggleMeasurement, CHANGE_MEASUREMENT_TOOL,
changeMeasurementState, CHANGE_MEASUREMENT_STATE,
changeUom, CHANGE_UOM,
changeGeometry, CHANGED_GEOMETRY
} = require('../measurement');
const feature = {type: "Feature", geometry: {
coordinates: [],
type: "LineString"
}};
const measureState = {
len: 84321231.123,
lengthFormula: "vincenty",
feature
};
describe('Test correctness of measurement actions', () => {

it('Test toggleMeasurement action creator', () => {
const retval = toggleMeasurement(measureState);
expect(retval).toExist();
expect(retval.type).toBe(CHANGE_MEASUREMENT_TOOL);
expect(retval.lengthFormula).toBe("vincenty");
});


it('Test changeMousePositionState action creator', () => {
const [uom, value, previousUom] = ["m", 42, {
length: {unit: 'km', label: 'km'},
area: {unit: 'sqm', label: 'm²'}
}];
const retval = changeUom(uom, value, previousUom);
expect(retval).toExist();
expect(retval.type).toBe(CHANGE_UOM);
expect(retval.uom).toBe("m");
expect(retval.value).toBe(42);
expect(retval.previousUom.length.label).toBe("km");
});

it('Test changeGeometry action creator', () => {

const retval = changeGeometry(feature);
expect(retval).toExist();
expect(retval.type).toBe(CHANGED_GEOMETRY);
expect(retval.feature.geometry.type).toBe("LineString");
});
it('Test changeMeasurementState action creator', () => {
const retval = changeMeasurementState(measureState);
expect(retval).toExist();
expect(retval.type).toBe(CHANGE_MEASUREMENT_STATE);
expect(retval.feature.geometry.type).toBe("LineString");
});

});
34 changes: 30 additions & 4 deletions web/client/actions/measurement.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/**
* Copyright 2015, GeoSolutions Sas.
/*
* Copyright 2018, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
*/
const CHANGE_MEASUREMENT_TOOL = 'CHANGE_MEASUREMENT_TOOL';
const CHANGE_MEASUREMENT_STATE = 'CHANGE_MEASUREMENT_STATE';
const CHANGE_UOM = 'MEASUREMENT:CHANGE_UOM';
const CHANGED_GEOMETRY = 'MEASUREMENT:CHANGED_GEOMETRY';

// TODO: the measurement control should use the "controls" state
function toggleMeasurement(measurement) {
Expand All @@ -22,6 +24,26 @@ function changeMeasurement(measurement) {
};
}

/**
* @param {string} uom length or area
* @param {string} value unit of uom
* @param {object} previous uom object
*/
function changeUom(uom, value, previousUom) {
return {
type: CHANGE_UOM,
uom,
value,
previousUom
};
}

function changeGeometry(feature) {
return {
type: CHANGED_GEOMETRY,
feature
};
}
function changeMeasurementState(measureState) {
return {
type: CHANGE_MEASUREMENT_STATE,
Expand All @@ -35,13 +57,17 @@ function changeMeasurementState(measureState) {
area: measureState.area,
bearing: measureState.bearing,
lenUnit: measureState.lenUnit,
areaUnit: measureState.areaUnit
areaUnit: measureState.areaUnit,
feature: measureState.feature
};
}

module.exports = {
CHANGE_MEASUREMENT_TOOL,
CHANGE_MEASUREMENT_STATE,
changeUom, CHANGE_UOM,
changeGeometry, CHANGED_GEOMETRY,
changeMeasurement,
toggleMeasurement,
changeMeasurementState
};
75 changes: 60 additions & 15 deletions web/client/components/map/leaflet/MeasurementSupport.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ const React = require('react');
const assign = require('object-assign');
var L = require('leaflet');
const {slice} = require('lodash');
var CoordinatesUtils = require('../../../utils/CoordinatesUtils');

const {reproject, calculateAzimuth, calculateDistance, transformLineToArcs} = require('../../../utils/CoordinatesUtils');
require('leaflet-draw');

class MeasurementSupport extends React.Component {
Expand Down Expand Up @@ -35,13 +34,13 @@ class MeasurementSupport extends React.Component {
if (newProps.measurement.geomType && newProps.measurement.geomType !== this.props.measurement.geomType ) {
this.addDrawInteraction(newProps);
}

if (!newProps.measurement.geomType) {
this.removeDrawInteraction();
}
}

onDrawStart = () => {
this.removeArcLayer();
this.drawing = true;
};

Expand All @@ -52,12 +51,19 @@ class MeasurementSupport extends React.Component {
// preserve the currently created layer to remove it later on
this.lastLayer = evt.layer;

let feature = this.lastLayer && this.lastLayer.toGeoJSON() || {};
if (this.props.measurement.geomType === 'Point') {
let pos = this.drawControl._marker.getLatLng();
let point = {x: pos.lng, y: pos.lat, srs: 'EPSG:4326'};
let newMeasureState = assign({}, this.props.measurement, {point: point});
let newMeasureState = assign({}, this.props.measurement, {point: point, feature});
this.props.changeMeasurementState(newMeasureState);
} else {
let newMeasureState = assign({}, this.props.measurement, {feature});
this.props.changeMeasurementState(newMeasureState);
}
if (this.props.measurement.lineMeasureEnabled && this.lastLayer) {
this.addArcsToMap([feature]);
}
};

render() {
Expand All @@ -66,10 +72,36 @@ class MeasurementSupport extends React.Component {
if (drawingStrings) {
L.drawLocal = drawingStrings;
}

return null;
}

/**
* This method adds arcs converting from a LineString features
*/
addArcsToMap = (features) => {
this.removeLastLayer();
let newFeatures = features.map(f => {
return assign({}, f, {
geometry: assign({}, f.geometry, {
coordinates: transformLineToArcs(f.geometry.coordinates)
})
});
});
this.arcLayer = L.geoJson(newFeatures, {
style: {
color: '#ffcc33',
opacity: 1,
weight: 1,
fillColor: '#ffffff',
fillOpacity: 0.2,
clickable: false
}
});
this.props.map.addLayer(this.arcLayer);
if (newFeatures && newFeatures.length > 0) {
this.arcLayer.addData(newFeatures);
}
}
updateMeasurementResults = () => {
if (!this.drawing || !this.drawControl) {
return;
Expand All @@ -79,10 +111,15 @@ class MeasurementSupport extends React.Component {
let bearing = 0;

let currentLatLng = this.drawControl._currentLatLng;
if (this.props.measurement.geomType === 'LineString' && this.drawControl._markers && this.drawControl._markers.length > 0) {
if (this.props.measurement.geomType === 'LineString' && this.drawControl._markers && this.drawControl._markers.length > 1) {
// calculate length
let previousLatLng = this.drawControl._markers[this.drawControl._markers.length - 1].getLatLng();
distance = this.drawControl._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng);
const reprojectedCoords = this.drawControl._markers.reduce((p, c) => {
const {lng, lat} = c.getLatLng();
return [...p, [lng, lat]];
}, []);

distance = calculateDistance(reprojectedCoords, this.props.measurement.lengthFormula);

} else if (this.props.measurement.geomType === 'Polygon' && this.drawControl._poly) {
// calculate area
let latLngs = [...this.drawControl._poly.getLatLngs(), currentLatLng];
Expand All @@ -98,12 +135,12 @@ class MeasurementSupport extends React.Component {
coords2 = [bearingMarkers[1].getLatLng().lng, bearingMarkers[1].getLatLng().lat];
}
// in order to align the results between leaflet and openlayers the coords are repojected only for leaflet
coords1 = CoordinatesUtils.reproject(coords1, 'EPSG:4326', this.props.projection);
coords2 = CoordinatesUtils.reproject(coords2, 'EPSG:4326', this.props.projection);
coords1 = reproject(coords1, 'EPSG:4326', this.props.projection);
coords2 = reproject(coords2, 'EPSG:4326', this.props.projection);
// calculate the azimuth as base for bearing information
bearing = CoordinatesUtils.calculateAzimuth(coords1, coords2, this.props.projection);
bearing = calculateAzimuth(coords1, coords2, this.props.projection);
}

// let drawn geom stay on the map
let newMeasureState = assign({}, this.props.measurement,
{
point: null, // Point is set in onDraw.created
Expand Down Expand Up @@ -199,9 +236,7 @@ class MeasurementSupport extends React.Component {
if (this.drawControl !== null && this.drawControl !== undefined) {
this.drawControl.disable();
this.drawControl = null;
if (this.lastLayer) {
this.props.map.removeLayer(this.lastLayer);
}
this.removeLastLayer();
this.props.map.off('draw:created', this.onDrawCreated, this);
this.props.map.off('draw:drawstart', this.onDrawStart, this);
this.props.map.off('click', this.mapClickHandler, this);
Expand All @@ -210,6 +245,16 @@ class MeasurementSupport extends React.Component {
}
}
};
removeLastLayer = () => {
if (this.lastLayer) {
this.props.map.removeLayer(this.lastLayer);
}
}
removeArcLayer = () => {
if (this.arcLayer) {
this.props.map.removeLayer(this.arcLayer);
}
}
}

module.exports = MeasurementSupport;
Loading

0 comments on commit dece305

Please sign in to comment.