@@ -7,6 +7,7 @@ var transform = require('./transform');
77var $ = require ( 'jquery' ) ;
88var Mousetrap = require ( 'mousetrap' ) ;
99var textFeature = require ( './textFeature' ) ;
10+ const lineAnnotation = require ( './annotation/lineAnnotation' ) ;
1011
1112/**
1213 * Object specification for an annotation layer.
@@ -223,7 +224,7 @@ var annotationLayer = function (arg) {
223224 */
224225 this . _handleBooleanOperation = function ( ) {
225226 const op = m_this . currentBooleanOperation ( ) ;
226- if ( ! op || ! m_this . currentAnnotation || ! m_this . currentAnnotation . toPolygonList ) {
227+ if ( ! op || ! m_this . currentAnnotation || ( op !== 'cut' && ! m_this . currentAnnotation . toPolygonList ) || ( op === 'cut' && ! ( m_this . currentAnnotation instanceof lineAnnotation ) ) ) {
227228 return ;
228229 }
229230 const newAnnot = m_this . currentAnnotation ;
@@ -235,11 +236,153 @@ var annotationLayer = function (arg) {
235236 } ;
236237 m_this . geoTrigger ( geo_event . annotation . boolean , evt ) ;
237238 if ( evt . cancel !== false ) {
238- util . polyops [ op ] ( m_this , newAnnot . toPolygonList ( ) , { correspond : { } , keepAnnotations : 'exact' , style : m_this } ) ;
239+ if ( op !== 'cut' ) {
240+ util . polyops [ op ] ( m_this , newAnnot . toPolygonList ( ) , { correspond : { } , keepAnnotations : 'exact' , style : m_this } ) ;
241+ } else {
242+ m_this . _cutOperation ( newAnnot ) ;
243+ }
239244 }
240245 }
241246 } ;
242247
248+ this . _extendLine = function ( p1 , p2 , bbox ) {
249+ const dx = p2 [ 0 ] - p1 [ 0 ] ;
250+ const dy = p2 [ 1 ] - p1 [ 1 ] ;
251+ if ( ! dx && ! dy ) {
252+ return p1 ;
253+ }
254+ let best ;
255+ let t = ( ( dx > 0 ? bbox . min . x : bbox . max . x ) - p1 [ 0 ] ) / ( dx || 1 ) ;
256+ if ( t < 0 ) {
257+ const y = p1 [ 1 ] + t * dy ;
258+ if ( y >= bbox . min . y && y <= bbox . max . y ) {
259+ best = [ dx > 0 ? bbox . min . x : bbox . max . x , y ] ;
260+ }
261+ }
262+ t = ( ( dy > 0 ? bbox . min . y : bbox . max . y ) - p1 [ 1 ] ) / ( dy || 1 ) ;
263+ if ( t < 0 ) {
264+ const x = p1 [ 0 ] + t * dx ;
265+ if ( x >= bbox . min . x && x <= bbox . max . x && ( ! best || t < ( bbox . min . x - p1 [ 0 ] ) / dx ) ) {
266+ best = [ x , dy > 0 ? bbox . min . y : bbox . max . y ] ;
267+ }
268+ }
269+ return best ;
270+ } ;
271+
272+ /**
273+ * Given a cut line, cut existing polygons and lines.
274+ *
275+ * @param {geo.annotation } cutLine The line to use to cut the existing
276+ * annotations.
277+ */
278+ this . _cutOperation = function ( cutLine ) {
279+ const cutPts = cutLine . coordinates ( null ) . map ( ( p ) => [ p . x , p . y ] ) ;
280+ let range ;
281+ for ( let p = 0 ; p < cutPts . length ; p += 1 ) {
282+ const x = cutPts [ p ] [ 0 ] ;
283+ const y = cutPts [ p ] [ 1 ] ;
284+ if ( ! p ) {
285+ range = { min : { x : x , y : y } , max : { x : x , y : y } } ;
286+ }
287+ if ( x < range . min . x ) { range . min . x = x ; }
288+ if ( y < range . min . y ) { range . min . y = y ; }
289+ if ( x > range . max . x ) { range . max . x = x ; }
290+ if ( y > range . max . y ) { range . max . y = y ; }
291+ }
292+ const polylist = m_this . toPolygonList ( ) ;
293+ for ( let poly = 0 ; poly < polylist . length ; poly += 1 ) {
294+ for ( let h = 0 ; h < polylist [ poly ] . length ; h += 1 ) {
295+ for ( let p = 0 ; p < polylist [ poly ] [ h ] . length ; p += 1 ) {
296+ const x = polylist [ poly ] [ h ] [ p ] [ 0 ] ;
297+ const y = polylist [ poly ] [ h ] [ p ] [ 1 ] ;
298+ if ( x < range . min . x ) { range . min . x = x ; }
299+ if ( y < range . min . y ) { range . min . y = y ; }
300+ if ( x > range . max . x ) { range . max . x = x ; }
301+ if ( y > range . max . y ) { range . max . y = y ; }
302+ }
303+ }
304+ }
305+ m_this . annotations ( ) . forEach ( ( annot ) => {
306+ if ( annot instanceof lineAnnotation ) {
307+ const pts = annot . coordinates ( null ) ;
308+ for ( let p = 0 ; p < pts . length ; p += 1 ) {
309+ const x = pts [ p ] . x ;
310+ const y = pts [ p ] . y ;
311+ if ( x < range . min . x ) { range . min . x = x ; }
312+ if ( y < range . min . y ) { range . min . y = y ; }
313+ if ( x > range . max . x ) { range . max . x = x ; }
314+ if ( y > range . max . y ) { range . max . y = y ; }
315+ }
316+ }
317+ } ) ;
318+ if ( range === undefined || range . min . x === range . max . x || range . min . y === range . max . y ) {
319+ return ;
320+ }
321+ // expand the range so that all polygons and lines, including our cut line
322+ // are guaranteed to be inside the bounding box.
323+ range = { min : {
324+ x : range . min . x - ( range . max . x - range . min . x ) * 0.01 ,
325+ y : range . min . y - ( range . max . y - range . min . y ) * 0.01
326+ } ,
327+ max : {
328+ x : range . max . x + ( range . max . x - range . min . x ) * 0.01 ,
329+ y : range . max . y + ( range . max . y - range . min . y ) * 0.01
330+ } } ;
331+ // we convert our line annotation so it expands past our bounding box, then
332+ // close it on the left / top. Our polygons will be the set that is cut
333+ // and the set that is union with this.
334+ cutPts [ 0 ] = m_this . _extendLine ( cutPts [ 0 ] , cutPts [ 1 ] , range ) ;
335+ cutPts [ cutPts . length - 1 ] = m_this . _extendLine ( cutPts [ cutPts . length - 1 ] , cutPts [ cutPts . length - 2 ] , range ) ;
336+ const cutPoly = cutPts . slice ( ) ;
337+ const corners = [ [ range . min . x , range . min . y ] , [ range . max . x , range . min . y ] , [ range . max . x , range . max . y ] , [ range . min . x , range . max . y ] ] ;
338+ const n = cutPoly . length - 1 ;
339+ const idx0 = cutPoly [ n ] [ 0 ] === range . min . x ? 0 : ( cutPoly [ n ] [ 1 ] === range . min . y ? 1 : ( cutPoly [ n ] [ 0 ] === range . max . x ? 2 : 3 ) ) ;
340+ const idx1 = cutPoly [ 0 ] [ 0 ] === range . min . x ? 0 : ( cutPoly [ 0 ] [ 1 ] === range . min . y ? 1 : ( cutPoly [ 0 ] [ 0 ] === range . max . x ? 2 : 3 ) ) ;
341+ for ( let idx = idx0 ; idx % 4 !== idx1 ; idx += 1 ) {
342+ cutPoly . push ( corners [ idx % 4 ] ) ;
343+ }
344+ // mimic some of what is done in fromPolygonList because we need both sides
345+ // of the cut.
346+ let diffPoly ;
347+ const annot = m_this . annotations ( ) ;
348+ const diff = { poly2 : [ [ cutPoly ] ] , correspond : { } , keepAnnotations : 'exact' , style : { fromPolygonList : ( poly , opts ) => { diffPoly = poly ; } } } ;
349+ diff . poly1 = m_this . toPolygonList ( diff ) ;
350+ util . polyops . difference ( diff ) ;
351+ util . polyops . intersect ( m_this , [ [ cutPoly ] ] , { correspond : { } , keepAnnotations : 'exact' , style : m_this } ) ;
352+ const indices = ( diff . annotationIndices || { } ) [ m_this . id ( ) ] ;
353+ const correspond = diff . correspond . poly1 ;
354+ const exact = diff . correspond . exact1 ;
355+ diffPoly . forEach ( ( p , idx ) => {
356+ p = p . map ( ( h ) => h . map ( ( pt ) => ( { x : pt [ 0 ] , y : pt [ 1 ] } ) ) ) ;
357+ const result = {
358+ vertices : p . length === 1 ? p [ 0 ] : { outer : p [ 0 ] , inner : p . slice ( 1 ) }
359+ } ;
360+ for ( let i = 0 ; i < correspond . length ; i += 1 ) {
361+ if ( correspond [ i ] && correspond [ i ] . indexOf ( idx ) >= 0 ) {
362+ const orig = annot [ indices [ i ] ] ;
363+ if ( exact [ i ] && exact [ i ] . indexOf ( idx ) >= 0 ) {
364+ m_this . addAnnotation ( orig , m_this . map ( ) . gcs ( ) , false ) ;
365+ return ;
366+ }
367+ [ 'name' , 'description' , 'label' ] . forEach ( ( k ) => {
368+ if ( orig [ k ] ( undefined , true ) ) {
369+ result [ k ] = orig [ k ] ( undefined , true ) ;
370+ }
371+ } ) ;
372+ Object . entries ( orig . options ( ) ) . forEach ( ( [ key , value ] ) => {
373+ if ( [ 'showLabel' , 'style' ] . indexOf ( key ) >= 0 || key . endsWith ( 'Style' ) ) {
374+ result [ key ] = value ;
375+ }
376+ } ) ;
377+ m_this . addAnnotation ( registry . createAnnotation ( 'polygon' , result ) , m_this . map ( ) . gcs ( ) , false ) ;
378+ return ;
379+ }
380+ }
381+ m_this . addAnnotation ( registry . createAnnotation ( 'polygon' , result ) , m_this . map ( ) . gcs ( ) , false ) ;
382+ } ) ;
383+ // TODO: cut lines
384+ } ;
385+
243386 /**
244387 * Handle updating the current annotation based on an update state.
245388 *
@@ -271,16 +414,22 @@ var annotationLayer = function (arg) {
271414 * @param {geo.event } evt The mouse move or click event.
272415 */
273416 this . _handleMouseMoveModifiers = function ( evt ) {
274- if ( m_this . mode ( ) !== m_this . modes . edit && m_this . currentAnnotation . options ( 'allowBooleanOperations' ) && ( m_this . currentAnnotation . _coordinates ( ) . length < 2 || m_this . mode ( ) === m_this . modes . cursor ) ) {
417+ const ops = m_this . currentAnnotation . options ( 'allowBooleanOperations' ) ;
418+ if ( m_this . mode ( ) !== m_this . modes . edit && ops && ( m_this . currentAnnotation . _coordinates ( ) . length < 2 || m_this . mode ( ) === m_this . modes . cursor ) ) {
275419 if ( evt . modifiers ) {
276420 const mod = ( evt . modifiers . shift ? 's' : '' ) + ( evt . modifiers . ctrl ? 'c' : '' ) + ( evt . modifiers . meta || evt . modifiers . alt ? 'a' : '' ) ;
277- if ( m_this . _currentBooleanClass === m_this . _booleanClasses [ mod ] ) {
421+ if ( mod === '' && ! m_this . _currentBooleanClass ) {
422+ return ;
423+ }
424+ const op = Object . keys ( m_this . _booleanClasses ) . find ( ( op ) =>
425+ m_this . _booleanClasses [ op ] === mod && ( ops === true || ops . includes ( op ) ) ) ;
426+ if ( m_this . _currentBooleanClass === op ) {
278427 return ;
279428 }
280- m_this . _currentBooleanClass = m_this . _booleanClasses [ mod ] ;
429+ m_this . _currentBooleanClass = op ;
281430 const mapNode = m_this . map ( ) . node ( ) ;
282- Object . values ( m_this . _booleanClasses ) . forEach ( ( c ) => {
283- mapNode . toggleClass ( c , m_this . _booleanClasses [ mod ] === c ) ;
431+ Object . keys ( m_this . _booleanClasses ) . forEach ( ( c ) => {
432+ mapNode . toggleClass ( c , op === c ) ;
284433 } ) ;
285434 }
286435 }
@@ -655,13 +804,14 @@ var annotationLayer = function (arg) {
655804 cursor : 'cursor'
656805 } ;
657806
658- /* Keys are short-hand for preferred event modifiers . Values are classes to
659- * apply to the map node . */
807+ /* Keys are classes to apply to the map node . Values are short-hand for
808+ * preferred event modifiers . */
660809 this . _booleanClasses = {
661- s : 'annotation-union' ,
662- sc : 'annotation-intersect' ,
663- c : 'annotation-difference' ,
664- sa : 'annotation-xor'
810+ 'annotation-union' : 's' ,
811+ 'annotation-intersect' : 'sc' ,
812+ 'annotation-difference' : 'c' ,
813+ 'annotation-xor' : 'sa' ,
814+ 'annotation-cut' : 'c'
665815 } ;
666816
667817 /**
@@ -696,7 +846,7 @@ var annotationLayer = function (arg) {
696846 m_mode = arg ;
697847 mapNode . toggleClass ( 'annotation-input' , ! ! ( m_mode && m_mode !== m_this . modes . edit && m_mode !== m_this . modes . cursor ) ) ;
698848 if ( ! m_mode || m_mode === m_this . modes . edit ) {
699- Object . values ( m_this . _booleanClasses ) . forEach ( ( c ) => mapNode . toggleClass ( c , false ) ) ;
849+ Object . keys ( m_this . _booleanClasses ) . forEach ( ( c ) => mapNode . toggleClass ( c , false ) ) ;
700850 m_this . _currentBooleanClass = undefined ;
701851 }
702852 if ( ! m_keyHandler ) {
0 commit comments