@arcgis/core
Version:
ArcGIS Maps SDK for JavaScript: A complete 2D and 3D mapping and data visualization API
3 lines (2 loc) • 10.7 kB
JavaScript
/* COPYRIGHT Esri - https://js.arcgis.com/5.0.8/LICENSE.txt */
import{__decorate as e}from"tslib";import{createTask as t}from"../../../core/asyncUtils.js";import n from"../../../core/Error.js";import{EventedAccessor as i}from"../../../core/Evented.js";import"../../../core/has.js";import{throwIfAborted as s,isAborted as a}from"../../../core/promiseUtils.js";import{watch as r,syncAndInitial as o,sync as p}from"../../../core/reactiveUtils.js";import{property as d,subclass as c}from"../../../core/accessorSupport/decorators.js";import{project as h,initializeProjection as l}from"../../../geometry/projectionUtils.js";import{absoluteHeightElevationInfo as u}from"../../../support/elevationInfoUtils.js";import{fromPoint as f,markAsTarget as g,toElevationAlignedDehydratedPoint as _}from"../sketch/normalizedPoint.js";import{defaults as S}from"./Settings.js";import m from"./SnappingOptions.js";import{sortCandidatesInPlace as v,squaredScreenDistance as y}from"./snappingUtils.js";import{DrapedEdgeSnappingCandidate as w}from"./candidates/DrapedEdgeSnappingCandidate.js";import{EdgeSnappingCandidate as C}from"./candidates/EdgeSnappingCandidate.js";import{IntersectionSnappingCandidate as P}from"./candidates/IntersectionSnappingCandidate.js";import{LineSnappingCandidate as T}from"./candidates/LineSnappingCandidate.js";import{ParallelLineSnappingCandidate as E}from"./candidates/ParallelLineSnappingCandidate.js";import{RightAngleSnappingCandidate as j}from"./candidates/RightAngleSnappingCandidate.js";import{RightAngleTriangleSnappingCandidate as R}from"./candidates/RightAngleTriangleSnappingCandidate.js";import{VertexSnappingCandidate as M}from"./candidates/VertexSnappingCandidate.js";import{DehydratedVertexSnappingCandidate as q}from"./candidates/dehydrated/DehydratedVertexSnappingCandidate.js";import{vectorToScreenPoint as x}from"../support/viewUtils.js";import{loadGeodesicLengthMeasurementUtils as b}from"../../support/geodesicLengthMeasurementUtils.js";let z=class extends i{constructor(e){super(e),this.options=new m,this._engineCache=new Map,this._loadTask=null,this._engines=[],this._currentMainCandidate=null,this._currentOtherActiveCandidates=[],this._currentSnappedType=0,this._currentSpatialReference=null}initialize(){this.addHandles([r(()=>{const{distance:e,touchSensitivityMultiplier:t,effectiveSelfEnabled:n,effectiveFeatureEnabled:i,effectiveGridEnabled:s}=this.options;return{selfEnabled:n,featureEnabled:i,gridEnabled:"2d"===this.view.type&&s,viewReady:this.view.ready,viewSpatialReference:this.view.spatialReference,distance:e,touchSensitivityMultiplier:t}},(e,n)=>{n&&(this.doneSnapping(),this.emit("changed")),this._loadTask?.abort(),this._loadTask=t(t=>this._updateEngines(e,n,t))},o),r(()=>this.options,e=>{for(const t of this._engines)t.options=e},p)])}destroy(){this._loadTask?.abort(),this._destroyEngines()}get updating(){return this._engines.some(e=>e.updating)||!this._loadTask?.finished}_destroyEngines(){this._engineCache.forEach(e=>e.destroy()),this._engineCache.clear(),this._engines=[]}async _updateEngines(e,t,n){if(!e.viewReady)return void this._destroyEngines();t?.viewSpatialReference!==e.viewSpatialReference&&this._destroyEngines();const i=this._engineCache,s=await Promise.allSettled([e.featureEnabled&&!i.has("feature")?this._createFeatureSnappingEngine(n):void 0,e.selfEnabled&&!i.has("self")?this._createSelfSnappingEngine(n):void 0,e.gridEnabled&&!i.has("grid")?this._createGridSnappingEngine(n):void 0]);if(n.aborted)for(const a of s)"fulfilled"===a.status&&a.value?.engine.destroy();else{for(const e of s)"fulfilled"===e.status&&e.value&&i.set(e.value.type,e.value.engine);this._engines=Array.from(i.values())}}async _createSelfSnappingEngine(e){const[{SelfSnappingEngine:t},n]=await Promise.all([import("./SelfSnappingEngine.js"),b()]);return s(e),{type:"self",engine:new t({view:this.view,options:this.options,geodesicLengthMeasurementUtils:n})}}async _createGridSnappingEngine(e){const{view:t}=this;if("2d"!==t.type)return;const{GridSnappingEngine:n}=await import("./GridSnappingEngine.js");return s(e),{type:"grid",engine:new n({view:t,options:this.options})}}async _createFeatureSnappingEngine(e){const{FeatureSnappingEngine:t}=await import("./FeatureSnappingEngine.js");s(e);const{view:n,options:i}=this,{spatialReference:a}=n;return{type:"feature",engine:new t({view:n,options:i,spatialReference:a})}}get _squaredMouseProximityThreshold(){return this.options.distance*this.options.distance}get _squaredTouchProximityThreshold(){const{distance:e,touchSensitivityMultiplier:t}=this.options,n=e*t;return n*n}getVertexCandidates(){const e=this._currentSpatialReference;if(!e)return[];const t=new Set;this._currentMainCandidate&&V(t,this._currentMainCandidate);for(const n of this._currentOtherActiveCandidates)V(t,n);return Array.from(t).map(t=>new q(t.objectId,t.layer,t.originalTargetPoint,e))}snap(e){return k(e)?this._snapMultiPoint(e):this._snapSinglePoint(e)}update(e){const{point:t,context:i}=e;this._removeVisualization();const s=this._currentMainCandidate;if(null==s)return t;const a=this._selectUpdateInput(e);if(null==a)return t;const{spatialReference:r}=i;if(!r.equals(this._currentSpatialReference))throw new n("snapping:mismatched-spatial-reference","Cannot update with the given spatial reference, as the current snapping candidates are in a different spatial reference.");const o=h(a,r);if(null==o)return t;const{view:p}=this,{elevationInfo:d,visualizer:c}=i,l=[],u=f(o,p,d),S=s.constraint.closestTo(u);if(!this._arePointsWithinScreenThreshold(u,S,i)||!U(s,i.drawConstraints))return this._resetSnappingState(),t;s.targetPoint=g(S),l.push(...s.hints);for(const n of this._currentOtherActiveCandidates)U(n,i.drawConstraints)&&!Z(s,n)&&(n.targetPoint=g(S),l.push(...n.hints));return null!=c&&this.addHandles(c.draw(l,{spatialReference:r,elevationInfo:D(i),view:p,selfSnappingZ:i.selfSnappingZ}),O),_(S,p,t,i)}doneSnapping(){this._removeVisualization(),this._resetSnappingState()}_selectUpdateInput({point:e,scenePoint:t}){switch(this._currentSnappedType){case 0:return e;case 1:return t}}_resetSnappingState(){this._currentMainCandidate=null,this._currentOtherActiveCandidates=[],this._currentSnappedType=0,this._currentSpatialReference=null}_removeVisualization(){this.removeHandles(O)}async _snapSinglePoint({point:e,context:t,signal:n}){const{view:i}=this,{elevationInfo:s}=t,a=f(e,i,s),r=await this._fetchCandidates(a,3,t,n);return this._createSnapResult(a,0,r,i,e,t,n)}async _snapMultiPoint({point:e,scenePoint:t,context:n,signal:i}){const{view:s}=this,{coordinateHelper:a,elevationInfo:r,spatialReference:o}=n;await l(t.spatialReference,o);const p=h(t,o),d=f(p,s,r),c=await this._fetchCandidates(d,1,n,i);if(c.length>0){const e=await this._fetchCandidates(d,2,n,i);return this._createSnapResult(d,1,[...c,...e],s,p,n,i)}const u=f(e,s,r),g=await this._fetchCandidates(u,2,n,i);return this._createSnapResult(u,0,g,s,{z:a.hasZ()&&e.hasZ?e.z??0:void 0,m:a.hasM()&&e.hasM?e.m??0:void 0},n,i)}async _fetchCandidates(e,t,n,i){return(await Promise.all(this._engines.map(s=>s.fetchCandidates(e,t,n,i)))).flat()}_createSnapResult(e,t,n,i,s,r,o){return{get valid(){return!a(o)},apply:()=>{const{spatialReference:a}=r,{snappedPoint:o,hints:p}=this._processCandidates(e,t,n,r);return this._removeVisualization(),null!=r.visualizer&&this.addHandles(r.visualizer.draw(p,{spatialReference:a,elevationInfo:u,view:i,selfSnappingZ:r.selfSnappingZ}),O),_(o,i,s,r)}}}_processCandidates(e,t,n,i){if(n.length<1)return this.doneSnapping(),{snappedPoint:e,hints:[]};this._currentSnappedType!==t&&this._resetSnappingState(),v(e,n);const s=this._currentMainCandidate;if(null!=s){const a=A(s,n);if(a>=0){if(!(n[a]instanceof P))return this._intersectWithOtherCandidates(a,n,e,t,i);if(this._arePointsWithinScreenThreshold(e,s.targetPoint,i))return this._updateSnappingCandidate(s,t,n,i)}}return this._intersectWithOtherCandidates(0,n,e,t,i)}_intersectWithOtherCandidates(e,t,n,i,s){const{coordinateHelper:a}=s,r=t[e],o=[];for(let p=0;p<t.length;++p){if(p===e)continue;const i=t[p],s=r.constraint.intersect(i.constraint);if(s)for(const e of s.closestPoints(r.targetPoint))o.push([new P(g(e),r,i,i.isDraped),this._squaredScreenDistance(n,e,a)])}return o.length>0&&(o.sort((e,t)=>e[1]-t[1]),o[0][1]<this._squaredPointProximityThreshold(s.pointer))?this._updateSnappingCandidate(o[0][0],i,t,s):U(r,s.drawConstraints)?this._updateSnappingCandidate(r,i,t,s):{snappedPoint:n,hints:[]}}_updateSnappingCandidate(e,t,n,i){this.doneSnapping(),this._currentMainCandidate=e,this._currentSnappedType=t,this._currentSpatialReference=i.spatialReference;const s=this._currentMainCandidate.targetPoint,a=[];a.push(...e.hints);for(const r of n){if(Z(e,r)){this._currentOtherActiveCandidates.push(r);continue}const t=r.constraint.closestTo(s);this._squaredScreenDistance(t,s,i.coordinateHelper)<I()&&(r.targetPoint=s,this._currentOtherActiveCandidates.push(r),a.push(...r.hints))}return{snappedPoint:s,hints:a}}_squaredPointProximityThreshold(e){return"touch"===e?this._squaredTouchProximityThreshold:this._squaredMouseProximityThreshold}_arePointsWithinScreenThreshold(e,t,n){return this._squaredScreenDistance(e,t,n.coordinateHelper)<this._squaredPointProximityThreshold(n.pointer)}_squaredScreenDistance(e,t,n){return y(this._toScreen(e,n),this._toScreen(t,n))}_toScreen(e,t){return x(e,t.spatialReference,u,this.view)}get test(){}};e([d({constructOnly:!0})],z.prototype,"view",void 0),e([d()],z.prototype,"options",void 0),e([d({readOnly:!0})],z.prototype,"updating",null),e([d()],z.prototype,"_loadTask",void 0),e([d()],z.prototype,"_engines",void 0),e([d()],z.prototype,"_squaredMouseProximityThreshold",null),e([d()],z.prototype,"_squaredTouchProximityThreshold",null),z=e([c("esri.views.interactive.snapping.SnappingManager")],z);const O="visualization-handle";function I(){return S.satisfiesConstraintScreenThreshold*S.satisfiesConstraintScreenThreshold}function U(e,t){return!t||null==t.direction&&null==t.distance||!(e instanceof w||e instanceof C||e instanceof T||e instanceof E||e instanceof R)&&(!(e instanceof j)||null==t.direction&&1===e.selfSnappingType)}function A(e,t){return e instanceof P?H(t,e.first)>=0&&H(t,e.second)>=0?0:-1:H(t,e)}function H(e,t){let n=-1;for(let i=0;i<e.length;++i)if(t.constraint.equals(e[i].constraint)){n=i;break}return n}function k(e){return null!=e.scenePoint}function D({coordinateHelper:e,elevationInfo:t}){return e.hasZ()?u:t}function V(e,t){t instanceof P&&(V(e,t.first),V(e,t.second)),t instanceof M&&null!=t.layer&&e.add(t)}function Z(e,t){return e instanceof P?t.constraint.equals(e.first.constraint)||t.constraint.equals(e.second.constraint):t.constraint.equals(e.constraint)}export{z as SnappingManager};