@arcgis/core
Version:
ArcGIS Maps SDK for JavaScript: A complete 2D and 3D mapping and data visualization API
6 lines (5 loc) • 10.7 kB
JavaScript
/*
All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://js.arcgis.com/4.32/esri/copyright.txt for details.
*/
import{memoize as e}from"../../../../../../../core/MapUtils.js";import{pt2px as t}from"../../../../../../../core/screenUtils.js";import{fromRotation as i,translate as s}from"../../../../../../../core/libs/gl-matrix-2/math/mat2d.js";import{create as r}from"../../../../../../../core/libs/gl-matrix-2/factories/mat2df32.js";import{transformMat2d as o,set as n,sub as a,normalize as l,add as h,scale as c}from"../../../../../../../core/libs/gl-matrix-2/math/vec2.js";import{fromValues as m}from"../../../../../../../core/libs/gl-matrix-2/factories/vec2f32.js";import{create as d}from"../../../../../../../core/libs/gl-matrix-2/factories/vec2f64.js";import{generalizeOptimizedGeometry as u,convertToGeometry as f}from"../../../../../../../layers/graphics/featureConversionUtils.js";import g from"../../../../../../../layers/graphics/OptimizedGeometry.js";import{getXAnchorDirection as p,getYAnchorDirection as _}from"../../../alignmentUtils.js";import{minMaxZoomPrecisionFactor as x}from"../../../definitions.js";import{getDisplayIdTexel as M}from"../../../DisplayId.js";import b from"../../../collisions/BoundingBox.js";import{LabelMetric as y}from"../../../collisions/LabelMetric.js";import{smoothPaths as v,pathDivide as P}from"../../../mesh/templates/segmentUtils.js";import{getMinMaxZoom as w,processColorInput as L}from"../fill/meshWriterUtils.js";import{TextMeshWriter as B,maxLabelZoom as S}from"../text/TextMeshWriter.js";const z=1,A=0,D=128,I=e((e=>{let t=0;if(0===e)return 1/0;for(;!(e%2);)t++,e/=2;return t}));class G extends B{constructor(){super(...arguments),this._zoomLevel=0}_write(e,t,i,s){if(this._zoomLevel=s||0,null!=i)throw new Error("InternalError: EffectGeometry not support for LabelMeshWriter");switch(t.geometryType){case"esriGeometryPoint":{const i=t.readXForDisplay(),s=t.readYForDisplay();this._writePoint(e,i,s,t);break}case"esriGeometryEnvelope":case"esriGeometryPolygon":case"esriGeometryMultipoint":{const i=t.readCentroidForDisplay();if(!i)return;const[s,r]=i.coords;this._writePoint(e,s,r,t);break}case"esriGeometryPolyline":this._writeLines(e,t)}}_getMetricDir(){const{horizontalAlignment:e,verticalAlignment:t}=this.evaluatedMeshParams;return["center"===e?0:"right"===e?-1:1,"middle"===t?0:"bottom"===t?-1:1]}_createLineLabelMetric(e,t,i,s){const r=M(e),[o,n]=this._getMetricDir(),a=this.evaluatedMeshParams.scaleInfo?.maxScale??0,l=this.evaluatedMeshParams.scaleInfo?.minScale??0;return new y(r,t,i,o,n,a,l,s??null)}_writePoint(e,t,i,s){const r=this._getShaping();if(!r)return;const o=s.getDisplayId(),n=p(this.evaluatedMeshParams.horizontalAlignment),a=_(this.evaluatedMeshParams.verticalAlignment),l=this.evaluatedMeshParams.scaleInfo?.maxScale??0,h=this.evaluatedMeshParams.scaleInfo?.minScale??0,c=M(s.getDisplayId()),m=this._getPointReferenceBounds()||{offsetX:0,offsetY:0,size:0};e.metricStart(new y(c,t,i,n,a,l,h,m)),this._writeGlyphs(e,o,t,i,r,0,m),e.metricBoxWrite(r.boundsT),e.metricEnd()}_getPointReferenceBounds(){if(!this._references)return null;for(const e of this._references){const t=e.getBoundsInfo();if(t)return t}return null}_writeLines(e,t){const{scaleInfo:i,verticalAlignment:s}=this.evaluatedMeshParams,r=this.evaluatedMeshParams.repeatLabelDistance||128,o=this._getShaping("middle");if(!o)return;const n=(e,t,i,s)=>this._placeSubdivGlyphs(e,t,i,s),a=(o.bounds.width+r)/(1<<z);this._current={out:e,id:t.getDisplayId(),shaping:o,zoomRange:w(i,this.getTileInfo()),referenceBounds:this._getPointReferenceBounds()||{offsetX:0,offsetY:0,size:0},offsetDirection:null},this._verticalPlacement="bottom"===s?"above":"top"===s?"below":null,this._verticalPlacement?this._writeAboveAndBelowAlong(t,n,a):this._writeCenterAlong(t,n,a)}_writeAboveAndBelowAlong(e,t,i){const{repeatLabel:s}=this.evaluatedMeshParams,{shaping:r}=this._current,o=r.bounds.halfHeight,n=e.readGeometryForDisplay();if(!n)return;const a=new g;u(a,n,!1,!1,"esriGeometryPolyline",1);const l=j(new g,a,o),h=j(new g,a,-o),c=f(h,"esriGeometryPolyline",!1,!1),m=f(l,"esriGeometryPolyline",!1,!1),d=v(m.paths,r.bounds.width),p=v(c.paths,r.bounds.width);this._current.offsetDirection="above";for(const u of d)P(u,i,t,!!s);this._current.offsetDirection="below";for(const u of p)P(u,i,t,!!s)}_writeCenterAlong(e,t,i){const{repeatLabel:s}=this.evaluatedMeshParams,{shaping:r}=this._current,o=v(e.readLegacyGeometryForDisplay().paths,r.bounds.width);for(const n of o)P(n,i,t,!!s)}_placeSubdivGlyphs(e,t,i,s){const{allowOverrun:r,labelPosition:o,repeatLabelDistance:n}=this.evaluatedMeshParams,a=this._current.zoomRange[0],l=I(t),h=this._current.shaping.bounds.width/(1<<z),c=Math.sqrt(n||D)/(1<<z),m=Math.min(i,s-i),d=this._current.shaping.isMultiline?S:Math.log2(m/(c+h/2)),u=0===t?d:Math.min(l,d),f=Math.max(a,this._zoomLevel+z-u),g=this._zoomLevel-f,p=this._current.shaping.bounds.width/2*2**g;this._current.shaping.isMultiline?0===t&&this._placeStraight(e,f):r&&g<0?this._placeStraightAlong(e,a):"parallel"===o?this._placeStraightAlong(e,f):"curved"===o&&this._placeCurved(e,f,p)}_placeStraight(e,t){const{out:i,id:s,shaping:r,referenceBounds:o}=this._current,{x:n,y:a}=e;i.metricStart(this._createLineLabelMetric(s,n,a)),i.metricBoxWrite(r.boundsT);const l=e.angle*(180/Math.PI)%360,h=(e.angle*(180/Math.PI)+180)%360;this._writeGlyphs(i,s,n,a,r,0,o,{clipAngle:l,mapAligned:!0,isLineLabel:!0,minZoom:t}),this._writeGlyphs(i,s,n,a,r,0,o,{clipAngle:h,mapAligned:!0,isLineLabel:!0,minZoom:t}),i.metricEnd()}_placeCurved(e,t,i){const{out:s,id:r}=this._current;s.metricStart(this._createLineLabelMetric(r,e.x,e.y));const o=e.clone(),n=e.angle*(180/Math.PI)%360,a=(e.angle*(180/Math.PI)+180)%360;this._verticalPlacement&&this._verticalPlacement!==this._current.offsetDirection||(this._placeFirst(o,t,1,n),this._placeBack(e,o,t,i,1,n),this._placeForward(e,o,t,i,1,n)),this._verticalPlacement&&this._verticalPlacement===this._current.offsetDirection||(this._placeFirst(o,t,0,a),this._placeBack(e,o,t,i,0,a),this._placeForward(e,o,t,i,0,a)),s.metricEnd()}_placeStraightAlong(e,o){const{out:n,id:a,shaping:l,zoomRange:h,referenceBounds:c}=this._current,{boxBorderLineColor:d,boxBackgroundColor:u}=this.evaluatedMeshParams,f=e.clone(),g=e.angle*(180/Math.PI)%360,p=(e.angle*(180/Math.PI)+180)%360,_=l.glyphs.length>0&&!(!d&&!u);if(n.metricStart(this._createLineLabelMetric(a,e.x,e.y)),_){const d=Math.max(o,h[0],0),u=Math.min(S,h[1]),f=i(r(),-e.angle),_={minZoom:d,maxZoom:u,clipAngle:g,mapAligned:!0,isLineLabel:!0},x=t(this.evaluatedMeshParams.offsetX),M=t(this.evaluatedMeshParams.offsetY);if(!this._verticalPlacement||this._verticalPlacement===this._current.offsetDirection){const t=m(x,-1*M),[i,o]=l.shapeBackground(s(r(),f,t));n.recordStart(this.instanceId,this.attributeLayout,l.glyphs[0].textureBinding);const h=2*Math.max(i.width,i.height);n.recordBounds(e.x+i.x,e.y+i.y,h,h),this._writeTextBox(n,a,e.x,e.y,o,c,_),n.recordEnd()}if(!this._verticalPlacement||this._verticalPlacement!==this._current.offsetDirection){const t=m(x,M),[i,o]=l.shapeBackground(s(r(),f,t));_.clipAngle=p,n.recordStart(this.instanceId,this.attributeLayout,l.glyphs[0].textureBinding);const h=2*Math.max(i.width,i.height);n.recordBounds(e.x+i.x,e.y+i.y,h,h),this._writeTextBox(n,a,e.x,e.y,o,c,_),n.recordEnd()}}this._verticalPlacement&&this._verticalPlacement!==this._current.offsetDirection||this._placeFirst(f,o,1,g,!0),this._verticalPlacement&&this._verticalPlacement===this._current.offsetDirection||this._placeFirst(f,o,0,p,!0),n.metricEnd()}_placeBack(e,t,i,s,r,o){const n=e.clone();let a=e.backwardLength+A;for(;n.prev()&&!(a>=s);)this._placeOnSegment(n,t,a,i,-1,r,o),a+=n.length+A}_placeForward(e,t,i,s,r,o){const n=e.clone();let a=e.remainingLength+A;for(;n.next()&&!(a>=s);)this._placeOnSegment(n,t,a,i,1,r,o),a+=n.length+A}_placeFirst(e,s,n,a,l=!1){const{out:h,id:c,shaping:d,zoomRange:u,referenceBounds:f}=this._current,g=d.glyphs,p=t(this.evaluatedMeshParams.offsetX),_=t(this.evaluatedMeshParams.offsetY),x=m(p,_),M=i(r(),-e.angle);o(x,x,M);for(const t of g){const i=t.x>d.bounds.x?n:1-n,r=i*e.remainingLength+(1-i)*e.backwardLength,o=Math.abs(t.x+t.width/2-d.bounds.x),m=Math.max(0,this._zoomLevel+Math.log2(o/(r+A))),g=Math.max(s,l?0:m);if(t.maxZoom=Math.min(u[1],S),t.angle=e.angle+(1-n)*Math.PI,t.minZoom=Math.max(u[0],g),this._writeLineGlyph(h,c,e.x,e.y,d.bounds,t,a,f,!0),(n||this._current.offsetDirection)&&this._isVisible(t.minZoom,t.maxZoom)){const e=new b(t.bounds.x+x[0],t.bounds.y+x[1],t.bounds.width,t.bounds.height);h.metricBoxWrite(e)}}}_placeOnSegment(e,s,n,a,l,h,c){const{out:d,id:u,shaping:f,referenceBounds:g}=this._current,p=f.glyphs,_=e.dx/e.length,x=e.dy/e.length,M={x:e.x+n*-l*_,y:e.y+n*-l*x},y=t(this.evaluatedMeshParams.offsetX),v=t(this.evaluatedMeshParams.offsetY),P=m(y,v),w=i(r(),-e.angle);o(P,P,w);for(const t of p){const i=t.x>f.bounds.x?h:1-h;if(!(i&&1===l||!i&&-1===l))continue;const s=Math.abs(t.x+t.width/2-f.bounds.x),r=Math.max(0,this._zoomLevel+Math.log2(s/n)-.1),o=Math.max(a,this._zoomLevel+Math.log2(s/(n+e.length+A)));if(0!==r&&(t.angle=e.angle+(1-h)*Math.PI,t.minZoom=o,t.maxZoom=r,this._writeLineGlyph(d,u,M.x,M.y,f.bounds,t,c,g,!0),(h||this._current.offsetDirection)&&this._isVisible(t.minZoom,t.maxZoom))){const e=new b(t.bounds.x+P[0],t.bounds.y+P[1],t.bounds.width,t.bounds.height);d.metricBoxWrite(e)}}}_writeLineGlyph(e,t,i,s,r,o,n,a,l){const h=i+r.x,c=s+r.y,m=2*(this.evaluatedMeshParams.minPixelBuffer?this.evaluatedMeshParams.minPixelBuffer/this._textMeshTransformProps.fontSize:1),d=Math.max(r.width,r.height)*m;e.recordStart(this.instanceId,this.attributeLayout,o.textureBinding),e.recordBounds(h,c,d,d);const{texcoords:u,offsets:f}=o,{fontSize:g,haloSize:p,outlineSize:_}=this._textMeshTransformProps;this._writeQuad(e,t,i,s,{texcoords:u,offsets:f,fontSize:g,haloSize:p,outlineSize:_,color:L(this.evaluatedMeshParams.color),isBackground:!1,referenceBounds:a,minZoom:Math.max(this._current.zoomRange[0],o.minZoom),maxZoom:Math.min(this._current.zoomRange[1],o.maxZoom),clipAngle:n,mapAligned:l,isLineLabel:!0}),e.recordEnd()}_isVisible(e,t){const i=Math.floor(this._zoomLevel*x)/x;return e<=i&&i<=t}}function j(e,t,i){const{coords:s,lengths:r}=t,o=d(),m=d(),u=d(),f=d(),g=d(),p=d(),_=2;let x=0;for(let d=0;d<r.length;d++){const t=r[d];for(let r=0;r<t;r++){const d=_*(r+x-1),M=_*(r+x),b=_*(r+x+1);r>0?n(o,s[d],s[d+1]):n(o,0,0),n(m,s[M],s[M+1]),r<t-1?n(u,s[b],s[b+1]):n(u,0,0),0===r?n(f,0,0):(a(f,m,o),l(f,f),n(f,f[1],-f[0])),r===t-1?n(g,0,0):(a(g,u,m),l(g,g),n(g,g[1],-g[0])),h(p,f,g),l(p,p);const y=p[0]*g[0]+p[1]*g[1];0!==y&&c(p,p,y),c(p,p,i),e.coords.push(m[0]+p[0],m[1]+p[1])}e.lengths.push(t),x+=t}return e}export{G as LabelMeshWriter};