77 createUnitSorter ,
88 ensureArray ,
99 ensureRealFloat ,
10+ PossiblyClippedUnit ,
1011} from "./utils" ;
12+ import { compareAgeRanges } from "@macrostrat/stratigraphy-utils" ;
1113
1214const dt = 0.001 ;
1315
@@ -111,6 +113,13 @@ export function groupUnitsIntoSections<T extends UnitLong>(
111113 units : T [ ] ,
112114 axisType : ColumnAxisType = ColumnAxisType . AGE
113115) : SectionInfo < T > [ ] {
116+ if ( axisType != ColumnAxisType . AGE ) {
117+ return groupUnitsIntoSectionByOverlap ( units , axisType ) ;
118+ }
119+
120+ /** Group units into sections by section_id.
121+ * This works for large-scale Macrostrat columns, where units are grouped by section_id.
122+ * */
114123 let groups = Array . from ( group ( units , ( d ) => d . section_id ) ) ;
115124 const unitComparator = createUnitSorter ( axisType ) ;
116125
@@ -130,6 +139,60 @@ export function groupUnitsIntoSections<T extends UnitLong>(
130139 return groups1 ;
131140}
132141
142+ interface WorkingSection {
143+ units : UnitLong [ ] ;
144+ // Position or age
145+ heightRange ?: [ number , number ] ;
146+ }
147+
148+ function groupUnitsIntoSectionByOverlap < T extends UnitLong > (
149+ units : T [ ] ,
150+ axisType : ColumnAxisType = ColumnAxisType . AGE
151+ ) : SectionInfo < T > [ ] {
152+ /** Group units into sections by overlap.
153+ * This creates "synthetic" sections that correspond to packages bound by scale gaps.
154+ * This is most useful in the height and depth domains, where gaps (e.g., missing core) are
155+ * common.
156+ * */
157+ // Start with each unit as its own "section", and progressively merge...
158+ const sectionList : WorkingSection [ ] = [ ] ;
159+ for ( const unit of units ) {
160+ // Check if the unit overlaps with any existing section
161+ const heightRange = getUnitHeightRange ( unit , axisType ) ;
162+ let section : WorkingSection | undefined = sectionList . find ( ( s ) =>
163+ compareAgeRanges ( heightRange , s . heightRange )
164+ ) ;
165+ if ( section == null ) {
166+ // No overlap, create a new section
167+ sectionList . push ( {
168+ heightRange,
169+ units : [ unit ] ,
170+ } ) ;
171+ } else {
172+ // Overlap, merge the unit into the section
173+ section . units . push ( unit ) ;
174+ // Update the height range
175+ section . heightRange = [
176+ Math . max ( section . heightRange [ 0 ] , heightRange [ 0 ] ) ,
177+ Math . min ( section . heightRange [ 1 ] , heightRange [ 1 ] ) ,
178+ ] ;
179+ }
180+ }
181+ // We should have a section for each unit, now we can convert to SectionInfo
182+ // Ages have to be really actually ages, not heights
183+
184+ return sectionList . map ( ( section , i ) => {
185+ const [ b_age , t_age ] = getSectionAgeRange ( section . units ) ;
186+ return {
187+ // Negative section IDs are used to indicate that these are synthetic sections
188+ section_id : - i ,
189+ t_age : t_age ,
190+ b_age : b_age ,
191+ units : section . units as T [ ] ,
192+ } ;
193+ } ) ;
194+ }
195+
133196export function getSectionAgeRange ( units : BaseUnit [ ] ) : [ number , number ] {
134197 /** Get the overall age range of a set of units. */
135198 const t_ages = units . map ( ( d ) => d . t_age ) ;
0 commit comments