@doegis/core
Version:
DOE GIS API
3 lines (1 loc) • 11.1 kB
JavaScript
import"../../../../../geometry.js";import e from"../../../../../core/Evented.js";import has from"../../../../../core/has.js";import{isNone as t,isSome as s,applySome as r}from"../../../../../core/maybe.js";import{diff as o,hasDiff as a}from"../../../../../core/accessorSupport/diffUtils.js";import{GeohashTree as i}from"../../../../../geohash/GeohashTree.js";import{setGeohashBuf as h,setGeohashXY as n}from"../../../../../geohash/geohashUtils.js";import{create as l}from"../../../../../geometry/support/aaBoundingBox.js";import{earth as u}from"../../../../../geometry/support/Ellipsoid.js";import{getInfo as g}from"../../../../../geometry/support/spatialReferenceUtils.js";import{getBoundsOptimizedGeometry as c,quantizeX as d,quantizeY as f,convertFromPolygon as p}from"../../../../../layers/graphics/featureConversionUtils.js";import{OptimizedFeatureWithGeometry as m,OptimizedFeature as _}from"../../../../../layers/graphics/OptimizedFeature.js";import y from"../../../../../layers/graphics/OptimizedGeometry.js";import{checkProjectionSupport as I,project as b}from"../../../../../layers/graphics/data/projectionSupport.js";import{TILE_SIZE as v}from"../../../engine/webgl/definitions.js";import{DISPLAY_ID_TEXEL_MASK as R}from"../../../engine/webgl/DisplayId.js";import{featureAdapter as M}from"../FeatureStore2D.js";import{Store2D as x}from"../Store2D.js";import{FeatureSetReaderJSON as C}from"./FeatureSetReaderJSON.js";import j from"../../../../../geometry/SpatialReference.js";import L from"../../../../../geometry/Polygon.js";import B from"../../../../../geometry/Extent.js";const F=12,w=64,S=1,V=l();class T extends m{constructor(e,t,s,r,o){super(new y([],[t,s]),r,null,e),this.geohashBoundsInfo=o}get count(){return this.attributes.cluster_count}static create(e,t,s,r,o,a,i,h){const n=new T(t,s,r,a,i);return n.displayId=e.createDisplayId(!0),n.referenceId=h,n.tileLevel=o,n}update(e,t,s,r,o,a){return this.geometry.coords[0]=e,this.geometry.coords[1]=t,this.tileLevel=s,this.attributes=r,this.geohashBoundsInfo=o,this.referenceId=null,this.referenceId=a,this}toJSON(){return{attributes:{...this.attributes,aggregateId:this.objectId,referenceId:1===this.attributes.cluster_count?this.referenceId:null},geometry:{x:this.geometry.coords[0],y:this.geometry.coords[1]}}}}function D(e){return 57.29577951308232*e}class G extends x{constructor(t,s,r,o){super(t,r),this.type="cluster",this.events=new e,this.objectIdField="aggregateId",this.featureAdapter=M,this._geohashLevel=0,this._tileLevel=0,this._aggregateValueRanges={},this._aggregateValueRangesChanged=!1,this._geohashBuf=[],this._clusters=new Map,this._tiles=new Map,this._serviceInfo=o,this.geometryInfo=t.geometryInfo,this._spatialReference=s,this._projectionSupportCheck=I(s,j.WGS84),this._bitsets.geohash=r.getBitset(r.createBitset()),this._bitsets.inserted=r.getBitset(r.createBitset())}destroy(){this._tree.destroy()}get featureSpatialReference(){return this._spatialReference}get fields(){return this._fields}async updateSchema(e,s){const r=this._schema;try{await super.updateSchema(e,s),await this._projectionSupportCheck}catch(n){}this._fields=this._schema.params.fields;const h=o(r,s);s&&(!t(h)||e.source||e.storage.filters)?((a(h,"params.fields")||!this._tree||e.source)&&(this._tree&&this._tree.destroy(),this._tree=new i(this._statisticFields,this._serviceInfo),this._rebuildTree(),has("esri-2d-update-debug")&&console.debug("Aggregate mesh needs update due to tree changing")),has("esri-2d-update-debug")&&console.debug("Applying Update - ClusterStore:",h),e.targets[s.name]=!0,e.mesh=!1,this._aggregateValueRanges={}):r&&(e.mesh=!0)}clear(){this._rebuildTree()}sweepFeatures(e,t){this._bitsets.inserted.forEachSet((s=>{if(!e.has(s)){const e=t.lookupByDisplayIdUnsafe(s);this._remove(e)}}))}sweepAggregates(e,t,s){this._clusters.forEach(((r,o)=>{r&&r.tileLevel!==s&&(e.releaseDisplayId(r.displayId),t.unsetAttributeData(r.displayId),this._clusters.delete(o))}))}onTileData(e,s,r,o,a=!0){if(!this._schema||t(s.addOrUpdate))return s;this.events.emit("changed");const i=this._getTransforms(e,this._spatialReference);{const e=s.addOrUpdate.getCursor();for(;e.next();)this._update(e,o)}if(s.status.mesh||!a)return s;const h=new Array,n=this._schema.params.clusterRadius;this._getClustersForTile(h,e,n,r,i),s.addOrUpdate=C.fromOptimizedFeatures(h,this._serviceInfo),s.addOrUpdate.attachStorage(r),s.clear=!0,s.end=!0;{const t=s.addOrUpdate.getCursor();for(;t.next();){const s=t.getDisplayId();this._bitsets.computed.unset(s),this.setComputedAttributes(r,t,s,e.scale)}}return this._aggregateValueRangesChanged&&s.end&&(this.events.emit("valueRangesChanged",{valueRanges:this._aggregateValueRanges}),this._aggregateValueRangesChanged=!1),s}onTileUpdate({added:e,removed:t}){if(e.length){const t=e[0].level;this._tileLevel=t,this._setGeohashLevel(t)}if(!this._schema)return;const s=this._schema.params.clusterRadius;t.forEach((e=>{this._tiles.delete(e.key.id),this._markTileClustersForDeletion(e,s)}))}getAggregate(e){for(const t of this._clusters.values())if((t?.displayId&R)==(e&R))return t.toJSON();return null}getAggregates(){const e=[];for(const t of this._clusters.values())t?.tileLevel===this._tileLevel&&e.push(t.toJSON());return e}getDisplayId(e){const t=this._clusters.get(e);return t?t.displayId:null}getFeatureDisplayIdsForAggregate(e){const t=this._clusters.get(e);return t?this._tree.getRegionDisplayIds(t.geohashBoundsInfo):[]}getDisplayIdForReferenceId(e){for(const t of this._clusters.values())if(t?.referenceId===e)return t.displayId;return null}getAggregateValueRanges(){return this._aggregateValueRanges}forEach(e){this._clusters.forEach((t=>{if(!t)return;const s=t.toJSON(),r=C.fromFeatures([s],{objectIdField:this.objectIdField,globalIdField:null,geometryType:this.geometryInfo.geometryType,fields:this.fields}).getCursor();r.next(),e(r)}))}forEachInBounds(e,t){}forEachBounds(e,s){const{hasM:r,hasZ:o}=this.geometryInfo;for(const a of e){const e=c(V,a.readGeometry(),o,r);t(e)||s(e)}}size(){let e=0;return this.forEach((t=>e++)),e}_rebuildTree(){this._bitsets.computed.clear(),this._bitsets.inserted.clear(),this._tree&&this._tree.clear()}_remove(e){const t=e.getDisplayId(),s=e.getXHydrated(),r=e.getYHydrated(),o=this._geohashBuf[2*t],a=this._geohashBuf[2*t+1];this._bitsets.inserted.has(t)&&(this._bitsets.inserted.unset(t),this._tree.removeCursor(e,s,r,o,a,this._geohashLevel))}_update(e,t){const s=e.getDisplayId(),r=this._bitsets.inserted,o=t.isVisible(s);if(o===r.has(s))return;if(!o)return void this._remove(e);const a=e.getXHydrated(),i=e.getYHydrated();if(!this._setGeohash(s,a,i))return;const h=this._geohashBuf[2*s],n=this._geohashBuf[2*s+1];this._tree.insertCursor(e,s,a,i,h,n,this._geohashLevel),r.set(s)}_setGeohash(e,t,s){if(this._bitsets.geohash.has(e))return!0;const r=this._geohashBuf;if(this._spatialReference.isWebMercator){const o=D(t/u.radius),a=o-360*Math.floor((o+180)/360),i=D(Math.PI/2-2*Math.atan(Math.exp(-s/u.radius)));h(r,e,i,a,F)}else{const o=b({x:t,y:s},this._spatialReference,j.WGS84);if(!o)return!1;h(r,e,o.y,o.x,F)}return this._bitsets.geohash.set(e),!0}_getClustersForTile(e,o,a,i,h,n=!0){const l=this._schema.params.clusterPixelBuffer,u=2*a,g=Math.ceil(2**o.key.level*v/u)+1,c=Math.ceil(l/u)+0,p=Math.ceil(v/u),{row:m,col:y}=o.key,I=y*v,b=m*v,R=Math.floor(I/u)-c,M=Math.floor(b/u)-c,x=R+p+2*c,C=M+p+2*c,j=o.tileInfoView.getLODInfoAt(o.key.level);for(let v=R;v<=x;v++)for(let a=M;a<=C;a++){let l=v;j.wrap&&(l=v<0?v+g:v%g);const u=j.wrap&&v<0,c=j.wrap&&v%g!==v,p=this._lookupCluster(i,j,o.key.level,l,a,o);if(s(p)){const o=r(h,(e=>u?e.left:c?e.right:e.tile));if(n&&t(o))continue;if(!p.count)continue;if(s(o)&&n){const t=p.geometry.clone();let r=p.attributes;t.coords[0]=d(o,t.coords[0]),t.coords[1]=f(o,t.coords[1]),1===p.count&&s(p.referenceId)&&(r={...p.attributes,referenceId:p.referenceId});const a=new _(t,r);a.displayId=p.displayId,e.push(a)}}}}_getGeohashLevel(e){return Math.min(Math.ceil(e/2+2),F)}_setGeohashLevel(e){const t=this._getGeohashLevel(e),s=(Math.floor(t/S)+1)*S-1;if(this._geohashLevel!==s)return this._geohashLevel=s,this._rebuildTree(),void this._bitsets.geohash.clear()}_getTransforms(e,t){const s={originPosition:"upperLeft",scale:[e.resolution,e.resolution],translate:[e.bounds[0],e.bounds[3]]},r=g(t);if(!r)return{tile:s,left:null,right:null};const[o,a]=r.valid;return{tile:s,left:{...s,translate:[a,e.bounds[3]]},right:{...s,translate:[o-a+e.bounds[0],e.bounds[3]]}}}_getClusterId(e,t,s){return(15&e)<<28|(16383&t)<<14|16383&s}_markForDeletion(e,t,s){const r=this._getClusterId(e,t,s);this._clusters.delete(r)}_getClusterBounds(e,t,s){const r=this._schema.params.clusterRadius,o=2*r;let a=s%2?t*o:t*o-r;const i=s*o;let h=a+o;const n=i-o,l=2**e.level*v;e.wrap&&a<0&&(a=0),e.wrap&&h>l&&(h=l);const u=a/v,g=i/v,c=h/v,d=n/v;return[e.getXForColumn(u),e.getYForRow(g),e.getXForColumn(c),e.getYForRow(d)]}_getGeohash(e,t,s){const r={geohashX:0,geohashY:0};return n(r,t,e,s),r}_getGeohashBounds(e,t){const s=this._getGeohashLevel(e.key.level);if(this._spatialReference.isWebMercator){const[e,r,o,a]=t,i={x:e,y:r},h={x:o,y:a};let l=0,g=0,c=0,d=0;{const e=D(i.x/u.radius);l=e-360*Math.floor((e+180)/360),g=D(Math.PI/2-2*Math.atan(Math.exp(-i.y/u.radius)))}{const e=D(h.x/u.radius);c=e-360*Math.floor((e+180)/360),d=D(Math.PI/2-2*Math.atan(Math.exp(-h.y/u.radius)))}const f={geohashX:0,geohashY:0},p={geohashX:0,geohashY:0};n(f,g,l,s),n(p,d,c,s);return{bounds:[e,r,o,a],geohashBounds:{xLL:f.geohashX,yLL:f.geohashY,xTR:p.geohashX,yTR:p.geohashY},level:s}}const r=L.fromExtent(B.fromBounds(t,this._spatialReference)),o=b(r,this._spatialReference,j.WGS84,{densificationStep:e.resolution*w});if(!o)return null;const a=p(new y,o,!1,!1),i=a.coords.filter(((e,t)=>!(t%2))),h=a.coords.filter(((e,t)=>t%2)),l=Math.min(...i),g=Math.min(...h),c=Math.max(...i),d=Math.max(...h),f=this._getGeohash(l,g,s),m=this._getGeohash(c,d,s);return{bounds:t,geohashBounds:{xLL:f.geohashX,yLL:f.geohashY,xTR:m.geohashX,yTR:m.geohashY},level:s}}_lookupCluster(e,r,o,a,i,h){const n=this._getClusterId(o,a,i),l=this._clusters.get(n),u=this._getClusterBounds(r,a,i),g=this._getGeohashBounds(h,u);if(t(g))return null;const c=this._tree.getRegionStatistics(g),{count:d,xTotal:f,yTotal:p,referenceId:m}=c,_=d?f/d:0,y=d?p/d:0;if(0===d)return this._clusters.set(n,null),null;const I={cluster_count:d,...c.attributes},b=s(l)?l.update(_,y,o,I,g,m):T.create(e,n,_,y,o,I,g,m);if(0===d){const[e,t,s,r]=u;b.geometry.coords[0]=(e+s)/2,b.geometry.coords[1]=(t+r)/2}return this._clusters.set(n,b),this._updateAggregateValueRangeForCluster(b,b.tileLevel),b}_updateAggregateValueRangeForCluster(e,t){const s=this._aggregateValueRanges[t]||{minValue:1/0,maxValue:0},r=s.minValue,o=s.maxValue;s.minValue=Math.min(r,e.count),s.maxValue=Math.max(o,e.count),this._aggregateValueRanges[t]=s,r===s.minValue&&o===s.maxValue||(this._aggregateValueRangesChanged=!0)}_markTileClustersForDeletion(e,t){const s=2*t,r=Math.ceil(v/s),{row:o,col:a}=e.key,i=a*v,h=o*v,n=Math.floor(i/s),l=Math.floor(h/s);for(let u=n;u<n+r;u++)for(let t=l;t<l+r;t++)this._markForDeletion(e.key.level,u,t)}}export{T as ClusterInfo,G as ClusterStore};