Skip to content

Commit 3bd2d7b

Browse files
committed
Merge branch '2626_measure_updates' into 2626_updates_measure_tool
2 parents 9043050 + eaaea9d commit 3bd2d7b

22 files changed

Lines changed: 1023 additions & 164 deletions

File tree

docs/developer-guide/local-config.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,26 @@ Set `selectedService` value to one of the ID of the services object ("Demo CSW S
143143
<br>Be careful to use unique IDs
144144
<br>Future implementations will try to detect the type from the url.
145145
<br>newService is used internally as the starting object for an empty service.
146+
147+
<br>
148+
<h4> Measure Tool configuration </h4>
149+
Inside defaultState you can set lengthFormula, showLabel, uom:
150+
- you can customize the formula used for length calculation from "haversine" or "vincenty" (default haversine)
151+
- show or not the measurement label on the map after drawing a measurement (default true)
152+
- set the default uom used for measure tool (default m and sqm)
153+
<br>For the label you can chose whatever value you want.
154+
<br>For the unit you can chose between:
155+
- unit length values : ft, m, km, mi, nm standing for feets, meters, kilometers, miles, nautical miles
156+
- unit area values : sqft, sqm, sqkm, sqmi, sqnm standing for square feets, square meters, square kilometers, square miles, square nautical miles
157+
158+
example:<br>
159+
```
160+
"measurement": {
161+
"lengthFormula": "vincenty",
162+
"showLabel": true,
163+
"uom": {
164+
"length": {"unit": "m", "label": "m"},
165+
"area": {"unit": "sqm", "label": "m²"}
166+
}
167+
}
168+
```

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"@carnesen/redux-add-action-listener-enhancer": "0.0.1",
8282
"@mapbox/togeojson": "0.16.0",
8383
"@turf/bbox": "4.1.0",
84+
"@turf/great-circle": "5.1.5",
8485
"@turf/inside": "4.1.0",
8586
"@turf/line-intersect": "4.1.0",
8687
"@turf/polygon-to-linestring": "4.1.0",
@@ -122,6 +123,7 @@
122123
"leaflet.nontiledlayer": "1.0.7",
123124
"lodash": "4.16.6",
124125
"moment": "2.13.0",
126+
"node-geo-distance": "1.2.0",
125127
"object-assign": "4.1.1",
126128
"ogc-schemas": "2.6.1",
127129
"openlayers": "4.6.4",
@@ -185,6 +187,7 @@
185187
"turf-bbox": "3.0.10",
186188
"turf-buffer": "3.0.10",
187189
"turf-intersect": "3.0.10",
190+
"turf-point": "2.0.1",
188191
"turf-point-on-surface": "3.0.10",
189192
"turf-union": "3.0.10",
190193
"url": "0.10.3",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2018, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
const expect = require('expect');
10+
const {
11+
toggleMeasurement, CHANGE_MEASUREMENT_TOOL,
12+
changeMeasurementState, CHANGE_MEASUREMENT_STATE,
13+
changeUom, CHANGE_UOM,
14+
changeGeometry, CHANGED_GEOMETRY
15+
} = require('../measurement');
16+
const feature = {type: "Feature", geometry: {
17+
coordinates: [],
18+
type: "LineString"
19+
}};
20+
const measureState = {
21+
len: 84321231.123,
22+
lengthFormula: "vincenty",
23+
feature
24+
};
25+
describe('Test correctness of measurement actions', () => {
26+
27+
it('Test toggleMeasurement action creator', () => {
28+
const retval = toggleMeasurement(measureState);
29+
expect(retval).toExist();
30+
expect(retval.type).toBe(CHANGE_MEASUREMENT_TOOL);
31+
expect(retval.lengthFormula).toBe("vincenty");
32+
});
33+
34+
35+
it('Test changeMousePositionState action creator', () => {
36+
const [uom, value, previousUom] = ["m", 42, {
37+
length: {unit: 'km', label: 'km'},
38+
area: {unit: 'sqm', label: 'm²'}
39+
}];
40+
const retval = changeUom(uom, value, previousUom);
41+
expect(retval).toExist();
42+
expect(retval.type).toBe(CHANGE_UOM);
43+
expect(retval.uom).toBe("m");
44+
expect(retval.value).toBe(42);
45+
expect(retval.previousUom.length.label).toBe("km");
46+
});
47+
48+
it('Test changeGeometry action creator', () => {
49+
50+
const retval = changeGeometry(feature);
51+
expect(retval).toExist();
52+
expect(retval.type).toBe(CHANGED_GEOMETRY);
53+
expect(retval.feature.geometry.type).toBe("LineString");
54+
});
55+
it('Test changeMeasurementState action creator', () => {
56+
const retval = changeMeasurementState(measureState);
57+
expect(retval).toExist();
58+
expect(retval.type).toBe(CHANGE_MEASUREMENT_STATE);
59+
expect(retval.feature.geometry.type).toBe("LineString");
60+
});
61+
62+
});

web/client/actions/measurement.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
/**
2-
* Copyright 2015, GeoSolutions Sas.
1+
/*
2+
* Copyright 2018, GeoSolutions Sas.
33
* All rights reserved.
44
*
55
* This source code is licensed under the BSD-style license found in the
66
* LICENSE file in the root directory of this source tree.
7-
*/
7+
*/
88
const CHANGE_MEASUREMENT_TOOL = 'CHANGE_MEASUREMENT_TOOL';
99
const CHANGE_MEASUREMENT_STATE = 'CHANGE_MEASUREMENT_STATE';
10+
const CHANGE_UOM = 'MEASUREMENT:CHANGE_UOM';
11+
const CHANGED_GEOMETRY = 'MEASUREMENT:CHANGED_GEOMETRY';
1012

1113
// TODO: the measurement control should use the "controls" state
1214
function toggleMeasurement(measurement) {
@@ -22,6 +24,26 @@ function changeMeasurement(measurement) {
2224
};
2325
}
2426

27+
/**
28+
* @param {string} uom length or area
29+
* @param {string} value unit of uom
30+
* @param {object} previous uom object
31+
*/
32+
function changeUom(uom, value, previousUom) {
33+
return {
34+
type: CHANGE_UOM,
35+
uom,
36+
value,
37+
previousUom
38+
};
39+
}
40+
41+
function changeGeometry(feature) {
42+
return {
43+
type: CHANGED_GEOMETRY,
44+
feature
45+
};
46+
}
2547
function changeMeasurementState(measureState) {
2648
return {
2749
type: CHANGE_MEASUREMENT_STATE,
@@ -35,13 +57,17 @@ function changeMeasurementState(measureState) {
3557
area: measureState.area,
3658
bearing: measureState.bearing,
3759
lenUnit: measureState.lenUnit,
38-
areaUnit: measureState.areaUnit
60+
areaUnit: measureState.areaUnit,
61+
feature: measureState.feature
3962
};
4063
}
4164

4265
module.exports = {
4366
CHANGE_MEASUREMENT_TOOL,
4467
CHANGE_MEASUREMENT_STATE,
68+
changeUom, CHANGE_UOM,
69+
changeGeometry, CHANGED_GEOMETRY,
4570
changeMeasurement,
71+
toggleMeasurement,
4672
changeMeasurementState
4773
};

web/client/components/map/leaflet/MeasurementSupport.jsx

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ const React = require('react');
33
const assign = require('object-assign');
44
var L = require('leaflet');
55
const {slice} = require('lodash');
6-
var CoordinatesUtils = require('../../../utils/CoordinatesUtils');
7-
6+
const {reproject, calculateAzimuth, calculateDistance, transformLineToArcs} = require('../../../utils/CoordinatesUtils');
87
require('leaflet-draw');
98

109
class MeasurementSupport extends React.Component {
@@ -35,13 +34,13 @@ class MeasurementSupport extends React.Component {
3534
if (newProps.measurement.geomType && newProps.measurement.geomType !== this.props.measurement.geomType ) {
3635
this.addDrawInteraction(newProps);
3736
}
38-
3937
if (!newProps.measurement.geomType) {
4038
this.removeDrawInteraction();
4139
}
4240
}
4341

4442
onDrawStart = () => {
43+
this.removeArcLayer();
4544
this.drawing = true;
4645
};
4746

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

54+
let feature = this.lastLayer && this.lastLayer.toGeoJSON() || {};
5555
if (this.props.measurement.geomType === 'Point') {
5656
let pos = this.drawControl._marker.getLatLng();
5757
let point = {x: pos.lng, y: pos.lat, srs: 'EPSG:4326'};
58-
let newMeasureState = assign({}, this.props.measurement, {point: point});
58+
let newMeasureState = assign({}, this.props.measurement, {point: point, feature});
59+
this.props.changeMeasurementState(newMeasureState);
60+
} else {
61+
let newMeasureState = assign({}, this.props.measurement, {feature});
5962
this.props.changeMeasurementState(newMeasureState);
6063
}
64+
if (this.props.measurement.lineMeasureEnabled && this.lastLayer) {
65+
this.addArcsToMap([feature]);
66+
}
6167
};
6268

6369
render() {
@@ -66,10 +72,36 @@ class MeasurementSupport extends React.Component {
6672
if (drawingStrings) {
6773
L.drawLocal = drawingStrings;
6874
}
69-
7075
return null;
7176
}
7277

78+
/**
79+
* This method adds arcs converting from a LineString features
80+
*/
81+
addArcsToMap = (features) => {
82+
this.removeLastLayer();
83+
let newFeatures = features.map(f => {
84+
return assign({}, f, {
85+
geometry: assign({}, f.geometry, {
86+
coordinates: transformLineToArcs(f.geometry.coordinates)
87+
})
88+
});
89+
});
90+
this.arcLayer = L.geoJson(newFeatures, {
91+
style: {
92+
color: '#ffcc33',
93+
opacity: 1,
94+
weight: 1,
95+
fillColor: '#ffffff',
96+
fillOpacity: 0.2,
97+
clickable: false
98+
}
99+
});
100+
this.props.map.addLayer(this.arcLayer);
101+
if (newFeatures && newFeatures.length > 0) {
102+
this.arcLayer.addData(newFeatures);
103+
}
104+
}
73105
updateMeasurementResults = () => {
74106
if (!this.drawing || !this.drawControl) {
75107
return;
@@ -79,10 +111,15 @@ class MeasurementSupport extends React.Component {
79111
let bearing = 0;
80112

81113
let currentLatLng = this.drawControl._currentLatLng;
82-
if (this.props.measurement.geomType === 'LineString' && this.drawControl._markers && this.drawControl._markers.length > 0) {
114+
if (this.props.measurement.geomType === 'LineString' && this.drawControl._markers && this.drawControl._markers.length > 1) {
83115
// calculate length
84-
let previousLatLng = this.drawControl._markers[this.drawControl._markers.length - 1].getLatLng();
85-
distance = this.drawControl._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng);
116+
const reprojectedCoords = this.drawControl._markers.reduce((p, c) => {
117+
const {lng, lat} = c.getLatLng();
118+
return [...p, [lng, lat]];
119+
}, []);
120+
121+
distance = calculateDistance(reprojectedCoords, this.props.measurement.lengthFormula);
122+
86123
} else if (this.props.measurement.geomType === 'Polygon' && this.drawControl._poly) {
87124
// calculate area
88125
let latLngs = [...this.drawControl._poly.getLatLngs(), currentLatLng];
@@ -98,12 +135,12 @@ class MeasurementSupport extends React.Component {
98135
coords2 = [bearingMarkers[1].getLatLng().lng, bearingMarkers[1].getLatLng().lat];
99136
}
100137
// in order to align the results between leaflet and openlayers the coords are repojected only for leaflet
101-
coords1 = CoordinatesUtils.reproject(coords1, 'EPSG:4326', this.props.projection);
102-
coords2 = CoordinatesUtils.reproject(coords2, 'EPSG:4326', this.props.projection);
138+
coords1 = reproject(coords1, 'EPSG:4326', this.props.projection);
139+
coords2 = reproject(coords2, 'EPSG:4326', this.props.projection);
103140
// calculate the azimuth as base for bearing information
104-
bearing = CoordinatesUtils.calculateAzimuth(coords1, coords2, this.props.projection);
141+
bearing = calculateAzimuth(coords1, coords2, this.props.projection);
105142
}
106-
143+
// let drawn geom stay on the map
107144
let newMeasureState = assign({}, this.props.measurement,
108145
{
109146
point: null, // Point is set in onDraw.created
@@ -199,9 +236,7 @@ class MeasurementSupport extends React.Component {
199236
if (this.drawControl !== null && this.drawControl !== undefined) {
200237
this.drawControl.disable();
201238
this.drawControl = null;
202-
if (this.lastLayer) {
203-
this.props.map.removeLayer(this.lastLayer);
204-
}
239+
this.removeLastLayer();
205240
this.props.map.off('draw:created', this.onDrawCreated, this);
206241
this.props.map.off('draw:drawstart', this.onDrawStart, this);
207242
this.props.map.off('click', this.mapClickHandler, this);
@@ -210,6 +245,16 @@ class MeasurementSupport extends React.Component {
210245
}
211246
}
212247
};
248+
removeLastLayer = () => {
249+
if (this.lastLayer) {
250+
this.props.map.removeLayer(this.lastLayer);
251+
}
252+
}
253+
removeArcLayer = () => {
254+
if (this.arcLayer) {
255+
this.props.map.removeLayer(this.arcLayer);
256+
}
257+
}
213258
}
214259

215260
module.exports = MeasurementSupport;

0 commit comments

Comments
 (0)