Skip to content

Commit f66cb65

Browse files
fix: Better support selecting features from results layer
1 parent e4e57cb commit f66cb65

2 files changed

Lines changed: 68 additions & 40 deletions

File tree

src/gm3/components/map/index.js

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ import {
6767
createLayer as createMeasureLayer,
6868
updateLayer as updateMeasureLayer,
6969
} from "./layers/measure";
70-
import { getMeasureLabelStyles } from "../measure/labels";
7170
import MapButton from "./button";
7271

7372
import { wfsGetFeatures } from "./layers/wfs";
@@ -137,10 +136,6 @@ class Map extends React.Component {
137136
// this is used when a feature isn't finished yet.
138137
this.sketchFeature = null;
139138

140-
// an overlay layer that annotates selected features with their
141-
// per-segment lengths when measure segment labels are enabled.
142-
this.measureLabelLayer = null;
143-
144139
// bound so it can be handed to the OL style functions and read the
145140
// latest config on every render.
146141
this.getMeasureLabelOptions = this.getMeasureLabelOptions.bind(this);
@@ -408,20 +403,6 @@ class Map extends React.Component {
408403
vectorLayer.applyStyle(this.selectionLayer, {
409404
layers: [{ on: true, style: this.props.selectionStyle }],
410405
});
411-
412-
// A sibling layer that shares the selection source and annotates each
413-
// selected feature's segments with their lengths. This is what makes
414-
// segment labels work when measuring polygons selected from other layers.
415-
this.measureLabelLayer = new VectorLayer({
416-
source: srcSelection,
417-
style: (feature) => {
418-
const labelOptions = this.getMeasureLabelOptions();
419-
if (!labelOptions.enabled) {
420-
return [];
421-
}
422-
return getMeasureLabelStyles(feature.getGeometry(), labelOptions);
423-
},
424-
});
425406
}
426407

427408
/** This is called after the first render.
@@ -471,7 +452,7 @@ class Map extends React.Component {
471452
// initialize the map.
472453
this.map = new olMap({
473454
target: this.mapDiv,
474-
layers: [this.selectionLayer, this.measureLabelLayer],
455+
layers: [this.selectionLayer],
475456
logo: false,
476457
view: new olView(viewParams),
477458
controls: getControls(this.props.config),
@@ -540,7 +521,6 @@ class Map extends React.Component {
540521

541522
this.olLayers = {};
542523
this.selectionLayer = null;
543-
this.measureLabelLayer = null;
544524
this.sketchFeature = null;
545525
}
546526

@@ -801,8 +781,8 @@ class Map extends React.Component {
801781
*/
802782
componentDidUpdate(prevProps) {
803783
// When the measure label options change (the toggle or the selected
804-
// units), force the affected layers to restyle. These changes do not
805-
// bump any layer's featuresVersion, so a manual redraw is needed.
784+
// units), force the measure layer to restyle. These changes do not
785+
// bump the layer's featuresVersion, so a manual redraw is needed.
806786
const mapView = this.props.mapView;
807787
const prevMapView = prevProps.mapView;
808788
if (
@@ -820,9 +800,6 @@ class Map extends React.Component {
820800
this.getMeasureLabelOptions
821801
);
822802
}
823-
if (this.measureLabelLayer) {
824-
this.measureLabelLayer.changed();
825-
}
826803
}
827804

828805
// extent takes precedent over the regular map-view,

src/gm3/components/measure/index.js

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { UnitOption, getComplementaryUnit } from "./unit";
4343

4444
import CoordinateDisplay from "../coordinate-display";
4545

46-
import { getSegmentInfo, normalizeGeometry, deconstructPolygon } from "./calc";
46+
import { getSegmentInfo, deconstructPolygon } from "./calc";
4747
import { colorizeFromIndex } from "./colorize";
4848

4949
const hasSqLabel = (unit) => unit !== "a" && unit !== "h";
@@ -109,6 +109,34 @@ export class MeasureTool extends Component {
109109
};
110110
this.props.saveFeature(this.props.targetLayer, nextFeature);
111111
}
112+
113+
// A feature was selected from another layer (the "Select" tool). Copy it
114+
// into the measure source so it becomes a first-class measure feature:
115+
// it picks up the measure-layer styling (per-segment and area
116+
// annotations for Polygon/MultiPolygon), a colorized swatch, a per-
117+
// feature remove button, and is cleared by "Clear measure features".
118+
// Then drop the ephemeral selection so it is not drawn twice.
119+
const selected = this.props.map.selectionFeatures;
120+
if (selected.length > 0 && selected !== prevProps.map.selectionFeatures) {
121+
const base = this.props.measureSource.features.length;
122+
selected.forEach((feature, i) => {
123+
// drop the source layer's id so the measure source assigns its own --
124+
// otherwise saveFeature treats the copy as an update to a feature that
125+
// does not exist in the measure source and never adds it.
126+
const { _uuid, ...properties } = feature.properties || {};
127+
this.props.saveFeature(this.props.targetLayer, {
128+
type: "Feature",
129+
// selection geometry is already in the map projection (EPSG:3857),
130+
// the same projection the measure source stores.
131+
geometry: feature.geometry,
132+
properties: {
133+
...properties,
134+
...colorizeFromIndex(base + i),
135+
},
136+
});
137+
});
138+
this.props.clearSelectionFeatures();
139+
}
112140
}
113141

114142
/* Render a LineString Measurement.
@@ -168,11 +196,13 @@ export class MeasureTool extends Component {
168196
<th>
169197
{this.props.t("measure-segment-length")} (
170198
{this.props.t(`units-${this.state.lengthUnits}`)})
171-
<RemoveFeatureButton
172-
removeFeature={this.props.removeFeature}
173-
targetLayer={this.props.targetLayer}
174-
properties={properties}
175-
/>
199+
{properties && (
200+
<RemoveFeatureButton
201+
removeFeature={this.props.removeFeature}
202+
targetLayer={this.props.targetLayer}
203+
properties={properties}
204+
/>
205+
)}
176206
</th>
177207
<th>{this.props.t("measure-bearing", "Bearing")}</th>
178208
</tr>
@@ -287,6 +317,25 @@ export class MeasureTool extends Component {
287317
live,
288318
properties
289319
);
320+
} else if (g.type === "MultiLineString") {
321+
// getSegmentInfo only understands a single LineString, so split the
322+
// multi-geometry into its parts and render a table per line. The
323+
// remove button is attached to the first part only so the whole
324+
// feature is not offered for removal once per line.
325+
const lonLat = projectFeatures([g], "EPSG:3857", "EPSG:4326")[0].geometry;
326+
return (
327+
<React.Fragment>
328+
{lonLat.coordinates.map((line, idx) => (
329+
<React.Fragment key={`part-${idx}`}>
330+
{this.renderSegments(
331+
{ type: "LineString", coordinates: line },
332+
live,
333+
idx === 0 ? properties : undefined
334+
)}
335+
</React.Fragment>
336+
))}
337+
</React.Fragment>
338+
);
290339
} else if (g.type === "Polygon" || g.type === "MultiPolygon") {
291340
// assume polygon
292341
return this.renderArea(g, live, properties);
@@ -296,11 +345,10 @@ export class MeasureTool extends Component {
296345
}
297346

298347
renderMeasureOutput() {
299-
let g = this.props.cursor.sketchGeometry;
300-
301-
if (g === null && this.props.map.selectionFeatures.length > 0) {
302-
g = normalizeGeometry(this.props.map.selectionFeatures);
303-
}
348+
// features selected from another layer are copied into the measure source
349+
// (see componentDidUpdate), so only the live sketch needs special casing
350+
// here -- everything else renders from measureSource.features below.
351+
const g = this.props.cursor.sketchGeometry;
304352

305353
const measureFeatures = this.props.measureSource.features;
306354

@@ -345,12 +393,15 @@ export class MeasureTool extends Component {
345393
const units = this.props.t("units");
346394
let measurementType = this.props.map.interactionType;
347395
if (measurementType === "Select") {
348-
if (this.props.map.selectionFeatures.length > 0) {
349-
measurementType = this.props.map.selectionFeatures[0].geometry.type;
396+
// the selected feature is copied into the measure source, so base the
397+
// unit options on the most recently measured feature's geometry.
398+
const measureFeatures = this.props.measureSource.features;
399+
if (measureFeatures.length > 0) {
400+
measurementType = measureFeatures[measureFeatures.length - 1].geometry.type;
350401
}
351402
}
352403

353-
if (measurementType === "LineString") {
404+
if (measurementType && measurementType.indexOf("LineString") >= 0) {
354405
return (
355406
<div className="measure-units">
356407
<b>{units}:</b>

0 commit comments

Comments
 (0)