@@ -43,7 +43,7 @@ import { UnitOption, getComplementaryUnit } from "./unit";
4343
4444import CoordinateDisplay from "../coordinate-display" ;
4545
46- import { getSegmentInfo , normalizeGeometry , deconstructPolygon } from "./calc" ;
46+ import { getSegmentInfo , deconstructPolygon } from "./calc" ;
4747import { colorizeFromIndex } from "./colorize" ;
4848
4949const 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