bruce-models 7.1.87 → 7.1.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9279,82 +9279,6 @@ var ClientFile;
9279
9279
  });
9280
9280
  }
9281
9281
  ClientFile.GetList = GetList;
9282
- function GetFindGeneratedTextureCacheKey(entityTypeSourceId, attribute) {
9283
- return `client-file-find-generated-texture-${entityTypeSourceId}-${attribute}`;
9284
- }
9285
- const GENERATED_TEXTURE_PURPOSE = "Polygon Texture, ValueMap";
9286
- const GENERATED_TEXTURE_SOURCE_PATH = "generation/Identity/EntityTypeSourceID";
9287
- const GENERATED_TEXTURE_ATTRIBUTE_PATH = "generation/Identity/Attribute";
9288
- /**
9289
- * Whether a file is the generated value map for this source and attribute.
9290
- */
9291
- function IsGeneratedTextureFor(file, entityTypeSourceId, attribute) {
9292
- var _a, _b;
9293
- const identity = (_b = (_a = file === null || file === void 0 ? void 0 : file.Data) === null || _a === void 0 ? void 0 : _a.generation) === null || _b === void 0 ? void 0 : _b.Identity;
9294
- if (!identity) {
9295
- return false;
9296
- }
9297
- // A source ID reaches us as a number from the API and as a string from a filter echo.
9298
- return Number(identity.EntityTypeSourceID) === Number(entityTypeSourceId) && identity.Attribute === attribute;
9299
- }
9300
- ClientFile.IsGeneratedTextureFor = IsGeneratedTextureFor;
9301
- /**
9302
- * Returns a generated texture file for given source ID + attribute combination.
9303
- */
9304
- function FindGeneratedTexture(params) {
9305
- return __awaiter(this, void 0, void 0, function* () {
9306
- let { api, entityTypeSourceId, attribute, req: reqParams } = params;
9307
- if (!api) {
9308
- api = ENVIRONMENT.Api().GetBruceApi();
9309
- }
9310
- const key = GetFindGeneratedTextureCacheKey(entityTypeSourceId, attribute);
9311
- const cache = api.GetCacheItem(key, reqParams);
9312
- if (cache === null || cache === void 0 ? void 0 : cache.found) {
9313
- return cache.data;
9314
- }
9315
- // Hack: API isn't returning the right file so we're adding to page size and filtering again on client. Patch ASAP.
9316
- const prom = (() => __awaiter(this, void 0, void 0, function* () {
9317
- const res = yield GetList({
9318
- api,
9319
- filter: {
9320
- PageSize: 300,
9321
- PageIndex: 0,
9322
- Purpose: GENERATED_TEXTURE_PURPOSE,
9323
- FilterRows: [
9324
- {
9325
- column: Filter.EColumn.Data,
9326
- path: GENERATED_TEXTURE_SOURCE_PATH,
9327
- rowOperator: Filter.ERowOperator.AND,
9328
- value: String(entityTypeSourceId),
9329
- includes: true,
9330
- valueOperator: "equals",
9331
- rows: []
9332
- },
9333
- {
9334
- column: Filter.EColumn.Data,
9335
- path: GENERATED_TEXTURE_ATTRIBUTE_PATH,
9336
- rowOperator: Filter.ERowOperator.AND,
9337
- value: attribute,
9338
- includes: true,
9339
- valueOperator: "equals",
9340
- rows: []
9341
- }
9342
- ]
9343
- },
9344
- req: reqParams
9345
- });
9346
- const match = (res.clientFiles || []).find((file) => IsGeneratedTextureFor(file, entityTypeSourceId, attribute));
9347
- return match || null;
9348
- }))();
9349
- api.SetCacheItem({
9350
- key,
9351
- value: prom,
9352
- req: reqParams
9353
- });
9354
- return prom;
9355
- });
9356
- }
9357
- ClientFile.FindGeneratedTexture = FindGeneratedTexture;
9358
9282
  /**
9359
9283
  * Deletes a list of client files matching the filter.
9360
9284
  * @param params
@@ -9446,6 +9370,660 @@ var ClientFile;
9446
9370
  ClientFile.GetCountsUserCacheKey = GetCountsUserCacheKey;
9447
9371
  })(ClientFile || (ClientFile = {}));
9448
9372
 
9373
+ /**
9374
+ * Reading what a published value map declares, without decoding a single pixel.
9375
+ */
9376
+ var ValueMapMetadata;
9377
+ (function (ValueMapMetadata) {
9378
+ // The only shape this library reads. The generations differ enough that a partial reading
9379
+ // produces plausible numbers rather than an error, which is why anything else is refused.
9380
+ ValueMapMetadata.SUPPORTED_VERSION = 3;
9381
+ // Byte references are published only under Tiles, so any other layout carries no readable pixels at all.
9382
+ ValueMapMetadata.LAYOUT_TILES = "tiles";
9383
+ ValueMapMetadata.LAYOUT_SINGLE = "single";
9384
+ // Value in R at 8 bits, or high in R and low in G at 16.
9385
+ ValueMapMetadata.FORMAT_U8 = "u8";
9386
+ ValueMapMetadata.FORMAT_RG16 = "rg16";
9387
+ // A flag in alpha rather than a number, so a mask layer declares no range.
9388
+ ValueMapMetadata.FORMAT_MASK = "mask";
9389
+ // Declared as a field with one value, so a second mode can be added later without an absent
9390
+ // Encoding having to mean anything.
9391
+ ValueMapMetadata.ENCODING_ABSOLUTE = "absolute";
9392
+ // One raster per entry in Series.Times.
9393
+ ValueMapMetadata.LAYER_VALUE = "Value";
9394
+ // Per-texel minimum and maximum over the whole series, as statistics rather than as part of
9395
+ // the encoding.
9396
+ ValueMapMetadata.LAYER_VALUE_MIN = "ValueMin";
9397
+ ValueMapMetadata.LAYER_VALUE_MAX = "ValueMax";
9398
+ // Texels that held their own minimum for the whole series, flagged in alpha.
9399
+ ValueMapMetadata.LAYER_BASELINE = "Baseline";
9400
+ // The per-texel correction applied to the source's numbers, on an archive that was transformed.
9401
+ ValueMapMetadata.LAYER_VALUE_SHIFT = "ValueShift";
9402
+ // A layer's alpha says whether a texel was sampled at all, so a transparent texel means no data
9403
+ // rather than a value of zero.
9404
+ // Alpha is per layer: masking one layer with another's coverage reports readings no texel holds.
9405
+ ValueMapMetadata.NO_DATA = 0;
9406
+ // Heights already on the ellipsoid, which are the only ones placeable without a shift.
9407
+ ValueMapMetadata.DATUM_ELLIPSOIDAL = "WGS84";
9408
+ ValueMapMetadata.DATUMS_ELLIPSOIDAL = ["WGS84", "ellipsoidal"];
9409
+ /**
9410
+ * The published block on a Client File record, or undefined when it carries none.
9411
+ */
9412
+ function Of(file) {
9413
+ const generation = file && file.Data ? file.Data.generation : undefined;
9414
+ return generation ? generation : undefined;
9415
+ }
9416
+ ValueMapMetadata.Of = Of;
9417
+ /**
9418
+ * Whether a record is a value map this library can read.
9419
+ */
9420
+ function IsReadable(metadata) {
9421
+ return Unreadable(metadata) === null;
9422
+ }
9423
+ ValueMapMetadata.IsReadable = IsReadable;
9424
+ /**
9425
+ * Why an archive cannot be read, or null when it can.
9426
+ *
9427
+ * Returned rather than thrown so a renderer can warn and draw nothing while an analysis caller
9428
+ * turns the same sentence into an error. Both then say the same thing.
9429
+ */
9430
+ function Unreadable(metadata) {
9431
+ if (!metadata) {
9432
+ return "This Client File carries no generation block, so it is not a generated value map.";
9433
+ }
9434
+ const at = metadata.Identity ? metadata.Identity.Generated : undefined;
9435
+ const generated = at ? " It was generated " + at + ", so regenerating it is the likely fix." : "";
9436
+ if (metadata.Version !== ValueMapMetadata.SUPPORTED_VERSION) {
9437
+ const found = metadata.Version != null ? String(metadata.Version) : "unstated";
9438
+ return ("This value map is generation " + found + " and this library reads only generation "
9439
+ + ValueMapMetadata.SUPPORTED_VERSION + "." + generated);
9440
+ }
9441
+ const encoding = metadata.Value ? metadata.Value.Encoding : undefined;
9442
+ if (encoding && encoding !== ValueMapMetadata.ENCODING_ABSOLUTE) {
9443
+ return ("This value map is encoded as " + encoding + ", and this library reads only "
9444
+ + ValueMapMetadata.ENCODING_ABSOLUTE + "." + generated);
9445
+ }
9446
+ const layout = metadata.Geometry ? metadata.Geometry.Layout : undefined;
9447
+ if (layout !== ValueMapMetadata.LAYOUT_TILES) {
9448
+ return ("This value map declares a " + (layout || "missing") + " layout, and generation "
9449
+ + ValueMapMetadata.SUPPORTED_VERSION + " publishes byte references only under Tiles, so there are no"
9450
+ + " pixels to read." + generated);
9451
+ }
9452
+ if (!(metadata.Tiles || []).length) {
9453
+ return ("This value map declares a tiled layout but publishes no tiles, so there are no"
9454
+ + " pixels to read." + generated);
9455
+ }
9456
+ return null;
9457
+ }
9458
+ ValueMapMetadata.Unreadable = Unreadable;
9459
+ /**
9460
+ * The tiles coarsest first, which is the order a reader has to composite them in.
9461
+ *
9462
+ * Tiles overlap and the finer one is the better answer, so assigning coarse first leaves the
9463
+ * finest tile holding each texel it covers.
9464
+ */
9465
+ function TilesOf(metadata) {
9466
+ const tiles = (metadata && metadata.Tiles ? metadata.Tiles : []).slice();
9467
+ tiles.sort((left, right) => {
9468
+ const byLevel = (left.Level || 0) - (right.Level || 0);
9469
+ return byLevel !== 0 ? byLevel : (right.TexelMetres || 0) - (left.TexelMetres || 0);
9470
+ });
9471
+ return tiles;
9472
+ }
9473
+ ValueMapMetadata.TilesOf = TilesOf;
9474
+ /**
9475
+ * One layer's declaration, or undefined when the archive does not carry it.
9476
+ */
9477
+ function LayerOf(metadata, name) {
9478
+ const layers = metadata && metadata.Layers ? metadata.Layers : [];
9479
+ for (const layer of layers) {
9480
+ if (layer && layer.Name === name) {
9481
+ return layer;
9482
+ }
9483
+ }
9484
+ return undefined;
9485
+ }
9486
+ ValueMapMetadata.LayerOf = LayerOf;
9487
+ /**
9488
+ * The range one layer was quantised between, or null for a mask and for a layer that is absent.
9489
+ *
9490
+ * A statistics layer is encoded over the true data range rather than over the frames' own, so
9491
+ * reading it with the frame range is a plausible wrong answer.
9492
+ */
9493
+ function LayerRange(metadata, name) {
9494
+ const layer = LayerOf(metadata, name);
9495
+ if (!layer || typeof layer.Min !== "number" || typeof layer.Max !== "number") {
9496
+ return null;
9497
+ }
9498
+ return { Min: layer.Min, Max: layer.Max };
9499
+ }
9500
+ ValueMapMetadata.LayerRange = LayerRange;
9501
+ /**
9502
+ * The values a reading out of this archive falls between, in its delivered units.
9503
+ */
9504
+ function ValueRange(metadata) {
9505
+ const value = (metadata && metadata.Value) || {};
9506
+ return {
9507
+ Min: typeof value.Min === "number" ? value.Min : 0,
9508
+ Max: typeof value.Max === "number" ? value.Max : 1
9509
+ };
9510
+ }
9511
+ ValueMapMetadata.ValueRange = ValueRange;
9512
+ /**
9513
+ * The values the source's own numbers fall between, or null where nothing was transformed.
9514
+ */
9515
+ function SourceRange(metadata) {
9516
+ const source = (metadata && metadata.ValueSource) || {};
9517
+ if (typeof source.Min !== "number" || typeof source.Max !== "number") {
9518
+ return null;
9519
+ }
9520
+ return { Min: source.Min, Max: source.Max };
9521
+ }
9522
+ ValueMapMetadata.SourceRange = SourceRange;
9523
+ /**
9524
+ * What was applied to the source's numbers to get the delivered ones, empty for nothing.
9525
+ */
9526
+ function TransformOf(metadata) {
9527
+ const source = metadata && metadata.ValueSource;
9528
+ return (source && source.Transform) || "";
9529
+ }
9530
+ ValueMapMetadata.TransformOf = TransformOf;
9531
+ /**
9532
+ * Whether a delivered reading differs from what the source's own data said.
9533
+ *
9534
+ * Both halves are needed: the transform names what happened, and the ValueShift layer is the
9535
+ * only thing that can undo it per texel.
9536
+ */
9537
+ function IsTransformed(metadata) {
9538
+ return !!TransformOf(metadata) && !!LayerOf(metadata, ValueMapMetadata.LAYER_VALUE_SHIFT);
9539
+ }
9540
+ ValueMapMetadata.IsTransformed = IsTransformed;
9541
+ /**
9542
+ * What a picture should be ramped over where the source suggests one, which is not a bound on the data.
9543
+ */
9544
+ function ColorBarRange(metadata) {
9545
+ const hints = (metadata && metadata.Hints) || {};
9546
+ if (typeof hints.ColorBarMin !== "number" || typeof hints.ColorBarMax !== "number") {
9547
+ return null;
9548
+ }
9549
+ return { Min: hints.ColorBarMin, Max: hints.ColorBarMax };
9550
+ }
9551
+ ValueMapMetadata.ColorBarRange = ColorBarRange;
9552
+ /**
9553
+ * When each frame was sampled. Authoritative, unlike Series.Increment.
9554
+ */
9555
+ function TimesOf(metadata) {
9556
+ const series = (metadata && metadata.Series) || {};
9557
+ return (series.Times || []).slice();
9558
+ }
9559
+ ValueMapMetadata.TimesOf = TimesOf;
9560
+ /**
9561
+ * Whether the archive holds every timestep that was asked for.
9562
+ *
9563
+ * A short archive is otherwise indistinguishable from a short series, and the failure it hides
9564
+ * is a skipped timestep in the middle of the event rather than a shorter one.
9565
+ */
9566
+ function IsComplete(metadata) {
9567
+ const series = (metadata && metadata.Series) || {};
9568
+ if (series.Complete != null) {
9569
+ return !!series.Complete;
9570
+ }
9571
+ const written = typeof series.Written === "number" ? series.Written : TimesOf(metadata).length;
9572
+ return (series.Requested || 0) === written;
9573
+ }
9574
+ ValueMapMetadata.IsComplete = IsComplete;
9575
+ /**
9576
+ * Says so when an archive's heights are not on the ellipsoid they will be drawn against.
9577
+ *
9578
+ * Returned rather than logged, so the renderer keeps its console warning and an analysis caller
9579
+ * can put the same sentence in a report.
9580
+ */
9581
+ function UnshiftedWarning(metadata) {
9582
+ const source = (metadata && metadata.ValueSource) || {};
9583
+ const datum = source.VerticalDatum;
9584
+ if (!datum || ValueMapMetadata.DATUMS_ELLIPSOIDAL.indexOf(datum) >= 0 || TransformOf(metadata)) {
9585
+ return null;
9586
+ }
9587
+ const at = metadata && metadata.Identity ? metadata.Identity.Generated : undefined;
9588
+ return ("These value map heights are in " + datum + " and report no transform, so they are"
9589
+ + " not ellipsoidal and will be wrong when placed absolutely."
9590
+ +
9591
+ (at ? " The archive was generated at " + at + " and needs regenerating with the shift"
9592
+ + " applied." : ""));
9593
+ }
9594
+ ValueMapMetadata.UnshiftedWarning = UnshiftedWarning;
9595
+ /**
9596
+ * Where one tile's copy of a layer sits in the blob, or null when it holds none.
9597
+ *
9598
+ * A per-time layer holds one entry per Series.Times element, and a zero length means the tile
9599
+ * held nothing at that time, so the indices stay aligned with the series.
9600
+ */
9601
+ function BytesOf(tile, name, index) {
9602
+ const entry = tile && tile.Layers ? tile.Layers[name] : undefined;
9603
+ if (!entry) {
9604
+ return null;
9605
+ }
9606
+ if (Array.isArray(entry)) {
9607
+ if (index == null || index < 0 || index >= entry.length) {
9608
+ return null;
9609
+ }
9610
+ const at = entry[index];
9611
+ return at && at.Bytes ? at.Bytes : null;
9612
+ }
9613
+ return entry.Bytes || null;
9614
+ }
9615
+ ValueMapMetadata.BytesOf = BytesOf;
9616
+ // How far apart two byte ranges can sit and still be worth reading together.
9617
+ // A wasted byte is far cheaper than a round trip.
9618
+ ValueMapMetadata.MAX_READ_GAP = 131072;
9619
+ /**
9620
+ * The span of the blob a set of byte ranges covers, or null for an empty set.
9621
+ */
9622
+ function SpanOf(ranges) {
9623
+ let low = Number.POSITIVE_INFINITY;
9624
+ let high = Number.NEGATIVE_INFINITY;
9625
+ for (const range of ranges) {
9626
+ if (!range || !range.Length) {
9627
+ continue;
9628
+ }
9629
+ low = Math.min(low, range.Offset);
9630
+ high = Math.max(high, range.Offset + range.Length);
9631
+ }
9632
+ return high > low ? { Offset: low, Length: high - low } : null;
9633
+ }
9634
+ ValueMapMetadata.SpanOf = SpanOf;
9635
+ /**
9636
+ * The rectangle the archive's raster covers, or null when the block does not place it.
9637
+ */
9638
+ function ExtentOf(metadata) {
9639
+ const geometry = (metadata && metadata.Geometry) || {};
9640
+ if (typeof geometry.West !== "number" || typeof geometry.East !== "number" ||
9641
+ typeof geometry.South !== "number" || typeof geometry.North !== "number") {
9642
+ return null;
9643
+ }
9644
+ return {
9645
+ West: geometry.West, East: geometry.East,
9646
+ South: geometry.South, North: geometry.North
9647
+ };
9648
+ }
9649
+ ValueMapMetadata.ExtentOf = ExtentOf;
9650
+ /**
9651
+ * Whether frames carry a 16 bit value packed across R and G.
9652
+ *
9653
+ * Worth asking by name: taking R alone from a packed frame yields a plausible surface quantised
9654
+ * to 255 steps of the full range rather than an obvious failure.
9655
+ */
9656
+ function IsPacked(metadata) {
9657
+ const value = (metadata && metadata.Value) || {};
9658
+ return value.Format === ValueMapMetadata.FORMAT_RG16;
9659
+ }
9660
+ ValueMapMetadata.IsPacked = IsPacked;
9661
+ /**
9662
+ * A value in the attribute's own units as the 0 to 1 position a ramp is indexed by.
9663
+ *
9664
+ * Stops are authored in real units, since "hide anything under 0.1 m" is the sentence a user
9665
+ * actually has, and the archive's published range is what makes that expressible.
9666
+ */
9667
+ function Normalise(value, range) {
9668
+ const span = range.Max - range.Min;
9669
+ if (!(span > 0)) {
9670
+ return 0;
9671
+ }
9672
+ return Math.min(1, Math.max(0, (value - range.Min) / span));
9673
+ }
9674
+ ValueMapMetadata.Normalise = Normalise;
9675
+ /**
9676
+ * A 0 to 1 position back in the attribute's own units.
9677
+ */
9678
+ function Denormalise(position, range) {
9679
+ return range.Min + position * (range.Max - range.Min);
9680
+ }
9681
+ ValueMapMetadata.Denormalise = Denormalise;
9682
+ /**
9683
+ * The packed number at one texel, straight out of the channels, whatever the format.
9684
+ */
9685
+ function PackedAt(pixels, at, format) {
9686
+ return format === ValueMapMetadata.FORMAT_RG16 ? pixels[at] * 256 + pixels[at + 1] : pixels[at];
9687
+ }
9688
+ ValueMapMetadata.PackedAt = PackedAt;
9689
+ /**
9690
+ * The 0 to 1 reading at one texel, for a caller indexing a ramp rather than reporting a number.
9691
+ */
9692
+ function NormalisedAt(pixels, at, format) {
9693
+ return PackedAt(pixels, at, format) / (format === ValueMapMetadata.FORMAT_RG16 ? 65535 : 255);
9694
+ }
9695
+ ValueMapMetadata.NormalisedAt = NormalisedAt;
9696
+ /**
9697
+ * The reads that cover a set of byte ranges, adjacent ones merged and distant ones left apart.
9698
+ *
9699
+ * What stops a caller having to choose between one round trip per entry and one download of
9700
+ * everything between the first entry and the last.
9701
+ */
9702
+ function PlanReads(ranges, maxGap) {
9703
+ const gap = maxGap == null ? ValueMapMetadata.MAX_READ_GAP : maxGap;
9704
+ const wanted = ranges
9705
+ .filter((at) => at && at.Length > 0)
9706
+ .slice()
9707
+ .sort((left, right) => left.Offset - right.Offset);
9708
+ const reads = [];
9709
+ for (const at of wanted) {
9710
+ const last = reads[reads.length - 1];
9711
+ const end = last ? last.Offset + last.Length : 0;
9712
+ if (last && at.Offset - end <= gap) {
9713
+ last.Length = Math.max(end, at.Offset + at.Length) - last.Offset;
9714
+ continue;
9715
+ }
9716
+ reads.push({ Offset: at.Offset, Length: at.Length });
9717
+ }
9718
+ return reads;
9719
+ }
9720
+ ValueMapMetadata.PlanReads = PlanReads;
9721
+ /**
9722
+ * A packed channel reading as the number it stands for.
9723
+ * value = Min + packed / fullScale * (Max - Min), where packed is R at 8 bits and R * 256 + G at 16.
9724
+ */
9725
+ function Decode(packed, format, range) {
9726
+ const fullScale = format === ValueMapMetadata.FORMAT_RG16 ? 65535 : 255;
9727
+ return range.Min + (packed / fullScale) * (range.Max - range.Min);
9728
+ }
9729
+ ValueMapMetadata.Decode = Decode;
9730
+ })(ValueMapMetadata || (ValueMapMetadata = {}));
9731
+
9732
+ /**
9733
+ * The mapping between a value map's raster and the world.
9734
+ * Turns a position, a bounding box or a ring into the texels they cover, and a texel back into a position.
9735
+ */
9736
+ var ValueMapGrid;
9737
+ (function (ValueMapGrid) {
9738
+ /**
9739
+ * A grid over one archive's extent.
9740
+ */
9741
+ class Grid {
9742
+ /**
9743
+ * A grid over a Geometry block, sized either by the block or by a composite passed in.
9744
+ */
9745
+ constructor(geometry, width, height, extent) {
9746
+ const block = Object.assign({}, geometry || {}, extent || {});
9747
+ const needed = ["West", "East", "South", "North"];
9748
+ if (width == null || height == null) {
9749
+ needed.push("ResolutionX", "ResolutionY");
9750
+ }
9751
+ for (const key of needed) {
9752
+ if (block[key] == null) {
9753
+ throw new Error("The value map's Geometry block has no " + key
9754
+ + ", so its raster cannot be placed on the world.");
9755
+ }
9756
+ }
9757
+ this.Width = width != null ? Math.round(width) : Math.round(block.ResolutionX);
9758
+ this.Height = height != null ? Math.round(height) : Math.round(block.ResolutionY);
9759
+ this.West = block.West;
9760
+ this.East = block.East;
9761
+ this.South = block.South;
9762
+ this.North = block.North;
9763
+ this.LonStep = (this.East - this.West) / this.Width;
9764
+ this.LatStep = (this.North - this.South) / this.Height;
9765
+ }
9766
+ /**
9767
+ * The position of a texel corner.
9768
+ */
9769
+ Corner(x, y) {
9770
+ return {
9771
+ Longitude: this.West + x * this.LonStep,
9772
+ Latitude: this.North - y * this.LatStep
9773
+ };
9774
+ }
9775
+ /**
9776
+ * The position at the middle of a texel, which is where its value was sampled.
9777
+ */
9778
+ Centre(row, column) {
9779
+ return {
9780
+ Longitude: this.West + (column + 0.5) * this.LonStep,
9781
+ Latitude: this.North - (row + 0.5) * this.LatStep
9782
+ };
9783
+ }
9784
+ /**
9785
+ * The texel covering a position, or null when it falls outside the raster.
9786
+ */
9787
+ TexelAt(longitude, latitude) {
9788
+ const column = Math.floor((longitude - this.West) / this.LonStep);
9789
+ const row = Math.floor((this.North - latitude) / this.LatStep);
9790
+ if (row >= 0 && row < this.Height && column >= 0 && column < this.Width) {
9791
+ return { Row: row, Column: column };
9792
+ }
9793
+ return null;
9794
+ }
9795
+ /**
9796
+ * A ring of texel corners as positions.
9797
+ */
9798
+ RingToPoints(ring) {
9799
+ return ring.map((corner) => this.Corner(corner.X, corner.Y));
9800
+ }
9801
+ /**
9802
+ * Every texel a bounding box covers, clipped to the raster.
9803
+ *
9804
+ * The whole box rather than its middle, since something smaller than a texel still lands on
9805
+ * two when it sits on the join, and reading only the centre would miss the other one.
9806
+ */
9807
+ TexelsIn(bounds) {
9808
+ if (!bounds) {
9809
+ return [];
9810
+ }
9811
+ const west = bounds.minLongitude;
9812
+ const east = bounds.maxLongitude;
9813
+ const south = bounds.minLatitude;
9814
+ const north = bounds.maxLatitude;
9815
+ if (west == null || east == null || south == null || north == null) {
9816
+ return [];
9817
+ }
9818
+ if (east < this.West || west > this.East || north < this.South || south > this.North) {
9819
+ return [];
9820
+ }
9821
+ // Clamped rather than dropped, so a box overlapping an edge keeps the part that is on
9822
+ // the raster instead of reporting nothing.
9823
+ const left = this.clampColumn((west - this.West) / this.LonStep);
9824
+ const right = this.clampColumn((east - this.West) / this.LonStep);
9825
+ const top = this.clampRow((this.North - north) / this.LatStep);
9826
+ const bottom = this.clampRow((this.North - south) / this.LatStep);
9827
+ const hits = [];
9828
+ for (let row = top; row <= bottom; row++) {
9829
+ for (let column = left; column <= right; column++) {
9830
+ hits.push({ Row: row, Column: column });
9831
+ }
9832
+ }
9833
+ return hits;
9834
+ }
9835
+ /**
9836
+ * Every texel a closed ring actually touches, rather than every texel its box covers.
9837
+ *
9838
+ * A box always claims at least as much as the shape inside it, so this can only ever take texels away.
9839
+ *
9840
+ * Slivers count: a ring clipping the corner of a texel has water there as much
9841
+ * as one sitting in the middle of it, so an edge crossing the cell is a hit on its own.
9842
+ */
9843
+ TexelsUnder(ring) {
9844
+ const points = (ring || []).map((point) => ({
9845
+ Longitude: point.Longitude,
9846
+ Latitude: point.Latitude
9847
+ }));
9848
+ if (points.length < 3) {
9849
+ return [];
9850
+ }
9851
+ const first = points[0];
9852
+ const last = points[points.length - 1];
9853
+ if (first.Longitude !== last.Longitude || first.Latitude !== last.Latitude) {
9854
+ points.push(first);
9855
+ }
9856
+ let minLon = Number.POSITIVE_INFINITY;
9857
+ let maxLon = Number.NEGATIVE_INFINITY;
9858
+ let minLat = Number.POSITIVE_INFINITY;
9859
+ let maxLat = Number.NEGATIVE_INFINITY;
9860
+ for (const point of points) {
9861
+ minLon = Math.min(minLon, point.Longitude);
9862
+ maxLon = Math.max(maxLon, point.Longitude);
9863
+ minLat = Math.min(minLat, point.Latitude);
9864
+ maxLat = Math.max(maxLat, point.Latitude);
9865
+ }
9866
+ if (maxLon < this.West || minLon > this.East || maxLat < this.South || minLat > this.North) {
9867
+ return [];
9868
+ }
9869
+ const left = this.clampColumn((minLon - this.West) / this.LonStep);
9870
+ const right = this.clampColumn((maxLon - this.West) / this.LonStep);
9871
+ const top = this.clampRow((this.North - maxLat) / this.LatStep);
9872
+ const bottom = this.clampRow((this.North - minLat) / this.LatStep);
9873
+ const hits = [];
9874
+ for (let row = top; row <= bottom; row++) {
9875
+ const north = this.North - row * this.LatStep;
9876
+ const south = north - this.LatStep;
9877
+ for (let column = left; column <= right; column++) {
9878
+ const west = this.West + column * this.LonStep;
9879
+ const east = west + this.LonStep;
9880
+ if (CellMeetsRing(points, west, east, south, north)) {
9881
+ hits.push({ Row: row, Column: column });
9882
+ }
9883
+ }
9884
+ }
9885
+ return hits;
9886
+ }
9887
+ clampColumn(at) {
9888
+ return Math.max(0, Math.min(this.Width - 1, Math.floor(at)));
9889
+ }
9890
+ clampRow(at) {
9891
+ return Math.max(0, Math.min(this.Height - 1, Math.floor(at)));
9892
+ }
9893
+ }
9894
+ ValueMapGrid.Grid = Grid;
9895
+ // Metres per degree at the equator, used only to work out how many texels an extent implies at a given ground resolution.
9896
+ const METRES_PER_DEGREE_LON = 111320;
9897
+ const METRES_PER_DEGREE_LAT = 110574;
9898
+ /**
9899
+ * The grid a set of tiles should be composited onto, sized so the finest one keeps its detail.
9900
+ *
9901
+ * Capped, because a pyramid's finest tile can imply a raster far larger than either an analysis
9902
+ * or a texture upload needs, and every frame pays for it.
9903
+ */
9904
+ function CompositeSize(tiles, extent, cap) {
9905
+ let finest = 0;
9906
+ for (const tile of tiles || []) {
9907
+ if (tile.TexelMetres > 0 && (!finest || tile.TexelMetres < finest)) {
9908
+ finest = tile.TexelMetres;
9909
+ }
9910
+ }
9911
+ if (!(finest > 0)) {
9912
+ finest = 1;
9913
+ }
9914
+ const middle = ((extent.South + extent.North) / 2) * Math.PI / 180;
9915
+ const widthMetres = Math.max((extent.East - extent.West) * METRES_PER_DEGREE_LON * Math.cos(middle), 1);
9916
+ const heightMetres = Math.max((extent.North - extent.South) * METRES_PER_DEGREE_LAT, 1);
9917
+ let width = Math.max(1, Math.ceil(widthMetres / finest));
9918
+ let height = Math.max(1, Math.ceil(heightMetres / finest));
9919
+ const longest = Math.max(width, height);
9920
+ if (cap > 0 && longest > cap) {
9921
+ const shrink = cap / longest;
9922
+ width = Math.max(1, Math.round(width * shrink));
9923
+ height = Math.max(1, Math.round(height * shrink));
9924
+ }
9925
+ return { Width: width, Height: height };
9926
+ }
9927
+ ValueMapGrid.CompositeSize = CompositeSize;
9928
+ /**
9929
+ * Where one tile lands on a composite of that size over that extent.
9930
+ */
9931
+ function TileRect(tile, extent, size) {
9932
+ const spanLon = extent.East - extent.West;
9933
+ const spanLat = extent.North - extent.South;
9934
+ const left = Math.round((tile.West - extent.West) / spanLon * size.Width);
9935
+ const right = Math.round((tile.East - extent.West) / spanLon * size.Width);
9936
+ const top = Math.round((extent.North - tile.North) / spanLat * size.Height);
9937
+ const bottom = Math.round((extent.North - tile.South) / spanLat * size.Height);
9938
+ return {
9939
+ Left: left,
9940
+ Top: top,
9941
+ Width: Math.max(1, right - left),
9942
+ Height: Math.max(1, bottom - top)
9943
+ };
9944
+ }
9945
+ ValueMapGrid.TileRect = TileRect;
9946
+ /**
9947
+ * Whether a position falls inside a closed ring, by the crossing count.
9948
+ */
9949
+ function PointInRing(points, longitude, latitude) {
9950
+ let inside = false;
9951
+ for (let at = 0; at < points.length - 1; at++) {
9952
+ const from = points[at];
9953
+ const to = points[at + 1];
9954
+ if ((from.Latitude > latitude) !== (to.Latitude > latitude)) {
9955
+ const crossing = from.Longitude
9956
+ + (latitude - from.Latitude) / (to.Latitude - from.Latitude)
9957
+ * (to.Longitude - from.Longitude);
9958
+ if (crossing > longitude) {
9959
+ inside = !inside;
9960
+ }
9961
+ }
9962
+ }
9963
+ return inside;
9964
+ }
9965
+ ValueMapGrid.PointInRing = PointInRing;
9966
+ /**
9967
+ * Whether a texel's rectangle and a closed ring overlap at all.
9968
+ */
9969
+ function CellMeetsRing(points, west, east, south, north) {
9970
+ if (PointInRing(points, (west + east) / 2, (south + north) / 2)) {
9971
+ return true;
9972
+ }
9973
+ for (const point of points) {
9974
+ if (point.Longitude >= west && point.Longitude <= east
9975
+ && point.Latitude >= south && point.Latitude <= north) {
9976
+ return true;
9977
+ }
9978
+ }
9979
+ const corners = [
9980
+ { Longitude: west, Latitude: south },
9981
+ { Longitude: east, Latitude: south },
9982
+ { Longitude: east, Latitude: north },
9983
+ { Longitude: west, Latitude: north },
9984
+ { Longitude: west, Latitude: south }
9985
+ ];
9986
+ for (let at = 0; at < points.length - 1; at++) {
9987
+ for (let side = 0; side < 4; side++) {
9988
+ if (SegmentsCross(points[at], points[at + 1], corners[side], corners[side + 1])) {
9989
+ return true;
9990
+ }
9991
+ }
9992
+ }
9993
+ return false;
9994
+ }
9995
+ ValueMapGrid.CellMeetsRing = CellMeetsRing;
9996
+ /**
9997
+ * Whether two segments intersect, including when one merely touches the other.
9998
+ */
9999
+ function SegmentsCross(a, b, c, d) {
10000
+ const side = (p, q, r) => (q.Longitude - p.Longitude) * (r.Latitude - p.Latitude) - (q.Latitude - p.Latitude) * (r.Longitude - p.Longitude);
10001
+ const d1 = side(c, d, a);
10002
+ const d2 = side(c, d, b);
10003
+ const d3 = side(a, b, c);
10004
+ const d4 = side(a, b, d);
10005
+ if (((d1 > 0) !== (d2 > 0)) && ((d3 > 0) !== (d4 > 0))) {
10006
+ return true;
10007
+ }
10008
+ // Touching counts, so a ring running exactly along a texel edge is not missed.
10009
+ return ((d1 === 0 && OnSegment(c, d, a)) ||
10010
+ (d2 === 0 && OnSegment(c, d, b)) ||
10011
+ (d3 === 0 && OnSegment(a, b, c)) ||
10012
+ (d4 === 0 && OnSegment(a, b, d)));
10013
+ }
10014
+ ValueMapGrid.SegmentsCross = SegmentsCross;
10015
+ /**
10016
+ * Whether a point already known to be on a segment's line lies between its ends.
10017
+ */
10018
+ function OnSegment(from, to, at) {
10019
+ return (at.Longitude >= Math.min(from.Longitude, to.Longitude) &&
10020
+ at.Longitude <= Math.max(from.Longitude, to.Longitude) &&
10021
+ at.Latitude >= Math.min(from.Latitude, to.Latitude) &&
10022
+ at.Latitude <= Math.max(from.Latitude, to.Latitude));
10023
+ }
10024
+ ValueMapGrid.OnSegment = OnSegment;
10025
+ })(ValueMapGrid || (ValueMapGrid = {}));
10026
+
9449
10027
  /**
9450
10028
  * GeoJSON data structures.
9451
10029
  */
@@ -9893,128 +10471,1838 @@ var Geometry;
9893
10471
  if (geo.Point) {
9894
10472
  merged.Point = geo.Point;
9895
10473
  }
9896
- else if (geo.LineString) {
9897
- merged.LineString = geo.LineString;
10474
+ else if (geo.LineString) {
10475
+ merged.LineString = geo.LineString;
10476
+ }
10477
+ else if (geo.Polygon) {
10478
+ merged.Polygon = geo.Polygon;
10479
+ }
10480
+ };
10481
+ full.MultiGeometry.forEach(addToMerged);
10482
+ return {
10483
+ geometry: merged,
10484
+ properties: properties
10485
+ };
10486
+ }
10487
+ // If there are many polygons but only 1 line and/or 1 point then we'll put those singles into the top-level.
10488
+ // Eg: 5 polygons, 1 point, 1 line. That should be a single geometry.
10489
+ if (countsPerType.Polygon > 1 && (countsPerType.LineString <= 1 && countsPerType.Point <= 1)) {
10490
+ const semiMerged = {};
10491
+ const addToSemiMerged = (geo) => {
10492
+ if (geo.Point) {
10493
+ semiMerged.Point = geo.Point;
10494
+ }
10495
+ else if (geo.LineString) {
10496
+ semiMerged.LineString = geo.LineString;
10497
+ }
10498
+ };
10499
+ semiMerged.MultiGeometry = full.MultiGeometry.filter(geo => geo.Polygon);
10500
+ full.MultiGeometry.forEach(addToSemiMerged);
10501
+ return {
10502
+ geometry: semiMerged,
10503
+ properties: properties
10504
+ };
10505
+ }
10506
+ // More than 1 of each, then return the full multi-geometry as-is.
10507
+ return {
10508
+ geometry: full,
10509
+ properties: properties
10510
+ };
10511
+ }
10512
+ Geometry.FromGeoJson = FromGeoJson;
10513
+ })(Geometry || (Geometry = {}));
10514
+
10515
+ var Bounds;
10516
+ (function (Bounds) {
10517
+ /**
10518
+ * Calculates boundaries from entity.
10519
+ * This will not use the entity's boundaries and instead calculate from geometry and location.
10520
+ * @param entity
10521
+ */
10522
+ function FromEntity(entity) {
10523
+ const bounds = {
10524
+ maxAltitude: null,
10525
+ maxLatitude: null,
10526
+ maxLongitude: null,
10527
+ minAltitude: null,
10528
+ minLatitude: null,
10529
+ minLongitude: null
10530
+ };
10531
+ const points = [];
10532
+ const assemblyLocation = Entity.GetValue({
10533
+ entity: entity,
10534
+ path: ["Bruce", "AssemblyLocation"]
10535
+ });
10536
+ if (assemblyLocation) {
10537
+ points.push(assemblyLocation);
10538
+ }
10539
+ const location = Entity.GetValue({
10540
+ entity: entity,
10541
+ path: ["Bruce", "Location"]
10542
+ });
10543
+ if (location) {
10544
+ points.push(location);
10545
+ }
10546
+ const geometry = Entity.GetValue({
10547
+ entity: entity,
10548
+ path: ["Bruce", "VectorGeometry"]
10549
+ });
10550
+ const processGeometry = (geometry, depth = 0) => {
10551
+ if (!geometry || depth > 5) {
10552
+ return;
10553
+ }
10554
+ if (geometry.Point) {
10555
+ points.push(...Geometry.ParsePoints(geometry.Point));
10556
+ }
10557
+ if (geometry.LineString) {
10558
+ points.push(...Geometry.ParsePoints(geometry.LineString));
10559
+ }
10560
+ if (geometry.Polygon) {
10561
+ for (const ring of geometry.Polygon) {
10562
+ points.push(...Geometry.ParsePoints(ring.LinearRing));
10563
+ }
10564
+ }
10565
+ if (geometry.MultiGeometry) {
10566
+ for (const multiGeometry of geometry.MultiGeometry) {
10567
+ processGeometry(multiGeometry, depth + 1);
10568
+ }
10569
+ }
10570
+ };
10571
+ processGeometry(geometry);
10572
+ for (const point of points) {
10573
+ if (bounds.maxLatitude == null || point.latitude > bounds.maxLatitude) {
10574
+ bounds.maxLatitude = point.latitude;
10575
+ }
10576
+ if (bounds.maxLongitude == null || point.longitude > bounds.maxLongitude) {
10577
+ bounds.maxLongitude = point.longitude;
10578
+ }
10579
+ if (bounds.maxAltitude == null || point.altitude > bounds.maxAltitude) {
10580
+ bounds.maxAltitude = point.altitude;
10581
+ }
10582
+ if (bounds.minLatitude == null || point.latitude < bounds.minLatitude) {
10583
+ bounds.minLatitude = point.latitude;
10584
+ }
10585
+ if (bounds.minLongitude == null || point.longitude < bounds.minLongitude) {
10586
+ bounds.minLongitude = point.longitude;
10587
+ }
10588
+ if (bounds.minAltitude == null || point.altitude < bounds.minAltitude) {
10589
+ bounds.minAltitude = point.altitude;
10590
+ }
10591
+ }
10592
+ return bounds;
10593
+ }
10594
+ Bounds.FromEntity = FromEntity;
10595
+ })(Bounds || (Bounds = {}));
10596
+
10597
+ /**
10598
+ * Reads a generated value map: its frames, the values in them, and the shapes those values make.
10599
+ */
10600
+ var ValueMapReader;
10601
+ (function (ValueMapReader) {
10602
+ /*
10603
+ Texels an area needs before it counts as one.
10604
+ A texel is a mesh sample rather than a measurement of ground, so a handful of them
10605
+ touching is usually the edge of the model flickering over the threshold.
10606
+ Pass 1 to keep every one of them.
10607
+ */
10608
+ ValueMapReader.MIN_TEXELS = 8;
10609
+ // How far a reduced outline may move from the traced one, in texels.
10610
+ ValueMapReader.SIMPLIFY_TOLERANCE = 0.25;
10611
+ // Fraction of the busiest frame that counts as the event being under way, used when picking
10612
+ // which frames are worth looking at.
10613
+ ValueMapReader.ONSET_FRACTION = 0.5;
10614
+ // Candidate levels walked when a threshold has to be read off the series.
10615
+ ValueMapReader.LEVELS = 256;
10616
+ // Longest edge of the grid tiles are composited onto. A pyramid's finest tile can imply a
10617
+ // raster far larger than the analysis needs, and every frame pays for it.
10618
+ ValueMapReader.MAX_COMPOSITE_TEXELS = 2048;
10619
+ // Decoded frames kept, since compositing a tiled frame costs one image decode per tile and the
10620
+ // passes over a series each walk the whole thing again.
10621
+ ValueMapReader.FRAME_CACHE = 16;
10622
+ // Single tile rasters kept, which is what a point sample reads instead of a whole composite.
10623
+ ValueMapReader.TILE_CACHE = 32;
10624
+ // How far apart two slices can sit and still be read together, from the format.
10625
+ ValueMapReader.MAX_READ_GAP = ValueMapMetadata.MAX_READ_GAP;
10626
+ class Reader {
10627
+ constructor(params) {
10628
+ this.frames = new Map();
10629
+ this.tileLayers = new Map();
10630
+ this.statistics = new Map();
10631
+ this.thresholds = new Map();
10632
+ this.baseline = undefined;
10633
+ this.unchanging = undefined;
10634
+ this.File = params.file;
10635
+ this.Metadata = ValueMapMetadata.Of(params.file);
10636
+ const refused = ValueMapMetadata.Unreadable(this.Metadata);
10637
+ if (refused) {
10638
+ // An unknown shape decodes into plausible numbers rather than failing, which is why
10639
+ // it is refused here instead of half read.
10640
+ throw new Error(refused);
10641
+ }
10642
+ this.Tiles = ValueMapMetadata.TilesOf(this.Metadata);
10643
+ this.bytes = params.bytes;
10644
+ this.decoder = params.decoder || ValueMapReader.DefaultDecoder;
10645
+ this.Extent = params.extent || ValueMapMetadata.ExtentOf(this.Metadata);
10646
+ if (!this.Extent) {
10647
+ throw new Error("The value map's Geometry block does not place its raster on the"
10648
+ + " world, so there is nowhere to read it onto.");
10649
+ }
10650
+ const size = ValueMapGrid.CompositeSize(this.Tiles, this.Extent, params.maxCompositeTexels || ValueMapReader.MAX_COMPOSITE_TEXELS);
10651
+ this.Grid = new ValueMapGrid.Grid(this.Metadata.Geometry, size.Width, size.Height, this.Extent);
10652
+ }
10653
+ /**
10654
+ * The attribute this value map was generated from.
10655
+ */
10656
+ get Attribute() {
10657
+ return this.identity().Attribute || null;
10658
+ }
10659
+ /**
10660
+ * What the attribute is called for a reader, where the source names it.
10661
+ */
10662
+ get Label() {
10663
+ return this.identity().Label || null;
10664
+ }
10665
+ /**
10666
+ * The dataset the values came from, since one account holds several over the same mesh.
10667
+ */
10668
+ get Dataset() {
10669
+ return this.identity().Dataset || null;
10670
+ }
10671
+ /**
10672
+ * The Entity Type Source this was generated from.
10673
+ */
10674
+ get EntityTypeSourceID() {
10675
+ const at = this.identity().EntityTypeSourceID;
10676
+ return at == null ? null : Number(at);
10677
+ }
10678
+ /**
10679
+ * When the archive was written, which is what tells two generations of it apart.
10680
+ */
10681
+ get Generated() {
10682
+ return this.identity().Generated || null;
10683
+ }
10684
+ get Revision() {
10685
+ const at = this.identity().Revision;
10686
+ return at == null ? null : Number(at);
10687
+ }
10688
+ /**
10689
+ * The units a reading is in, where the source states them.
10690
+ */
10691
+ get Units() {
10692
+ return (this.Metadata.Value || {}).Units || null;
10693
+ }
10694
+ /**
10695
+ * How a frame's number is packed: one channel or two.
10696
+ */
10697
+ get Format() {
10698
+ return (this.Metadata.Value || {}).Format || ValueMapMetadata.FORMAT_U8;
10699
+ }
10700
+ /**
10701
+ * The values a reading out of this value map falls between, in its own units.
10702
+ */
10703
+ get ValueRange() {
10704
+ return ValueMapMetadata.ValueRange(this.Metadata);
10705
+ }
10706
+ /**
10707
+ * The values the source's own numbers fall between, or null where nothing was transformed.
10708
+ */
10709
+ get SourceRange() {
10710
+ return ValueMapMetadata.SourceRange(this.Metadata);
10711
+ }
10712
+ /**
10713
+ * What the source's own heights are measured from, when the archive says.
10714
+ * A thickness declares none, and an untransformed elevation declares one with no transform.
10715
+ */
10716
+ get VerticalDatum() {
10717
+ return (this.Metadata.ValueSource || {}).VerticalDatum || null;
10718
+ }
10719
+ /**
10720
+ * What was applied to the source's numbers to get the delivered ones, empty for nothing.
10721
+ */
10722
+ get Transform() {
10723
+ return ValueMapMetadata.TransformOf(this.Metadata);
10724
+ }
10725
+ /**
10726
+ * Whether a reading differs from what the source's own data said.
10727
+ */
10728
+ get IsTransformed() {
10729
+ return ValueMapMetadata.IsTransformed(this.Metadata);
10730
+ }
10731
+ /**
10732
+ * What the source suggests a picture be ramped over, which is not a bound on the data.
10733
+ */
10734
+ get ColorBarRange() {
10735
+ return ValueMapMetadata.ColorBarRange(this.Metadata);
10736
+ }
10737
+ /**
10738
+ * The largest movement any one texel made over the series.
10739
+ */
10740
+ get MovingRangeMax() {
10741
+ const stats = this.Metadata.Stats || {};
10742
+ return typeof stats.MovingRangeMax === "number" ? stats.MovingRangeMax : null;
10743
+ }
10744
+ /**
10745
+ * How many timesteps the generator set out to write.
10746
+ */
10747
+ get RequestedFrames() {
10748
+ return (this.Metadata.Series || {}).Requested || 0;
10749
+ }
10750
+ /**
10751
+ * How many it actually wrote.
10752
+ */
10753
+ get WrittenFrames() {
10754
+ const series = this.Metadata.Series || {};
10755
+ return typeof series.Written === "number" ? series.Written : this.Times.length;
10756
+ }
10757
+ /**
10758
+ * Whether the archive holds every timestep that was asked for.
10759
+ */
10760
+ get IsComplete() {
10761
+ return ValueMapMetadata.IsComplete(this.Metadata);
10762
+ }
10763
+ /**
10764
+ * When each frame was sampled. Authoritative, unlike the series increment.
10765
+ */
10766
+ get Times() {
10767
+ return ValueMapMetadata.TimesOf(this.Metadata);
10768
+ }
10769
+ /**
10770
+ * How many frames the archive holds.
10771
+ */
10772
+ get Length() {
10773
+ return this.Times.length;
10774
+ }
10775
+ /**
10776
+ * Says so when the archive's heights are not on the ellipsoid, or null when they are.
10777
+ */
10778
+ Warning() {
10779
+ return ValueMapMetadata.UnshiftedWarning(this.Metadata);
10780
+ }
10781
+ /**
10782
+ * One layer's declaration, or undefined when the archive does not carry it.
10783
+ */
10784
+ Layer(name) {
10785
+ return ValueMapMetadata.LayerOf(this.Metadata, name);
10786
+ }
10787
+ /**
10788
+ * One frame's values and a mask of which texels were sampled.
10789
+ */
10790
+ Frame(index = 0, source = false) {
10791
+ return __awaiter(this, void 0, void 0, function* () {
10792
+ const position = this.position(index);
10793
+ const key = position + ":" + source;
10794
+ const held = this.frames.get(key);
10795
+ if (held) {
10796
+ this.frames.delete(key);
10797
+ this.frames.set(key, held);
10798
+ return held;
10799
+ }
10800
+ let field = yield this.composite(ValueMapMetadata.LAYER_VALUE, position);
10801
+ if (!field) {
10802
+ throw new Error("Frame " + index + " has no image in any of this value map's "
10803
+ + this.Tiles.length + " tiles.");
10804
+ }
10805
+ if (source) {
10806
+ field = yield this.toSource(field);
10807
+ }
10808
+ this.frames.set(key, field);
10809
+ Trim(this.frames, ValueMapReader.FRAME_CACHE);
10810
+ return field;
10811
+ });
10812
+ }
10813
+ /**
10814
+ * Each texel's lowest value over the series and where that is known, or null.
10815
+ */
10816
+ ValueMin() {
10817
+ return this.statistic(ValueMapMetadata.LAYER_VALUE_MIN);
10818
+ }
10819
+ /**
10820
+ * Each texel's highest value over the series and where that is known, or null.
10821
+ */
10822
+ ValueMax() {
10823
+ return this.statistic(ValueMapMetadata.LAYER_VALUE_MAX);
10824
+ }
10825
+ /**
10826
+ * The per-texel correction applied to the source's own numbers, or null where none was.
10827
+ *
10828
+ * sourceValue = value - shift, which is what makes the transform reversible per texel rather
10829
+ * than only describable in prose.
10830
+ */
10831
+ ValueShift() {
10832
+ return this.statistic(ValueMapMetadata.LAYER_VALUE_SHIFT);
10833
+ }
10834
+ /**
10835
+ * The texels flagged as permanently at their own minimum, or null when none were published.
10836
+ *
10837
+ * A caller measuring change subtracts these rather than reporting ground that never moved.
10838
+ */
10839
+ Baseline() {
10840
+ return __awaiter(this, void 0, void 0, function* () {
10841
+ if (this.baseline !== undefined) {
10842
+ return this.baseline;
10843
+ }
10844
+ this.baseline = null;
10845
+ if (this.Layer(ValueMapMetadata.LAYER_BASELINE)) {
10846
+ const field = yield this.composite(ValueMapMetadata.LAYER_BASELINE);
10847
+ if (field) {
10848
+ this.baseline = { Width: field.Width, Height: field.Height, Bits: field.Covered };
10849
+ }
10850
+ }
10851
+ return this.baseline;
10852
+ });
10853
+ }
10854
+ /**
10855
+ * The texels holding one value for the whole series, or null for a single frame archive.
10856
+ */
10857
+ Unchanging() {
10858
+ return __awaiter(this, void 0, void 0, function* () {
10859
+ if (this.unchanging !== undefined) {
10860
+ return this.unchanging;
10861
+ }
10862
+ this.unchanging = null;
10863
+ if (this.Length < 2) {
10864
+ return this.unchanging;
10865
+ }
10866
+ let lowest = null;
10867
+ let highest = null;
10868
+ let always = null;
10869
+ let width = 0;
10870
+ let height = 0;
10871
+ for (let index = 0; index < this.Length; index++) {
10872
+ const frame = yield this.Frame(index);
10873
+ width = frame.Width;
10874
+ height = frame.Height;
10875
+ if (!lowest) {
10876
+ lowest = frame.Values.slice();
10877
+ highest = frame.Values.slice();
10878
+ always = frame.Covered.slice();
10879
+ continue;
10880
+ }
10881
+ for (let at = 0; at < lowest.length; at++) {
10882
+ lowest[at] = Math.min(lowest[at], frame.Values[at]);
10883
+ highest[at] = Math.max(highest[at], frame.Values[at]);
10884
+ // A texel sampled in only some frames is changing, whatever its values did.
10885
+ always[at] = always[at] && frame.Covered[at] ? 1 : 0;
10886
+ }
10887
+ }
10888
+ const bits = new Uint8Array(always.length);
10889
+ for (let at = 0; at < bits.length; at++) {
10890
+ bits[at] = always[at] && lowest[at] === highest[at] ? 1 : 0;
10891
+ }
10892
+ this.unchanging = { Width: width, Height: height, Bits: bits };
10893
+ return this.unchanging;
10894
+ });
10895
+ }
10896
+ /**
10897
+ * Every texel a change in the series cannot be read from, or null when there are none.
10898
+ *
10899
+ * The published baseline and the texels measured as unchanging, since neither describes
10900
+ * anything the series does and a caller asking what moved wants both gone.
10901
+ */
10902
+ Ignored() {
10903
+ return __awaiter(this, void 0, void 0, function* () {
10904
+ const baseline = yield this.Baseline();
10905
+ const unchanging = yield this.Unchanging();
10906
+ if (!baseline) {
10907
+ return unchanging;
10908
+ }
10909
+ if (!unchanging) {
10910
+ return baseline;
10911
+ }
10912
+ return Combine(baseline, unchanging, (left, right) => left || right);
10913
+ });
10914
+ }
10915
+ /**
10916
+ * The level the series itself separates on, for a caller who cannot name one.
10917
+ */
10918
+ NaturalThreshold(source = false) {
10919
+ return __awaiter(this, void 0, void 0, function* () {
10920
+ if (this.thresholds.has(source)) {
10921
+ return this.thresholds.get(source);
10922
+ }
10923
+ const range = (source && this.SourceRange) || this.ValueRange;
10924
+ this.thresholds.set(source, range.Min);
10925
+ if (this.Length < 2) {
10926
+ return range.Min;
10927
+ }
10928
+ const step = (range.Max - range.Min) / (ValueMapReader.LEVELS - 1);
10929
+ if (step <= 0) {
10930
+ return range.Min;
10931
+ }
10932
+ const ignored = yield this.Ignored();
10933
+ const above = [];
10934
+ for (let index = 0; index < this.Length; index++) {
10935
+ const frame = yield this.Frame(index, source);
10936
+ const counts = new Float64Array(ValueMapReader.LEVELS);
10937
+ for (let at = 0; at < frame.Values.length; at++) {
10938
+ if (!frame.Covered[at] || (ignored && ignored.Bits[at])) {
10939
+ continue;
10940
+ }
10941
+ const level = Math.max(0, Math.min(ValueMapReader.LEVELS - 1, Math.round((frame.Values[at] - range.Min) / step)));
10942
+ counts[level]++;
10943
+ }
10944
+ // Texels strictly above each level, which is what a mask at that level would hold.
10945
+ const holding = new Array(ValueMapReader.LEVELS);
10946
+ let running = 0;
10947
+ for (let level = ValueMapReader.LEVELS - 1; level >= 0; level--) {
10948
+ holding[level] = running;
10949
+ running += counts[level];
10950
+ }
10951
+ above.push(holding);
10952
+ }
10953
+ let best = 0;
10954
+ let widest = 0;
10955
+ for (let level = 0; level < ValueMapReader.LEVELS; level++) {
10956
+ const spread = Deviation(above.map((counts) => counts[level]));
10957
+ if (spread > widest) {
10958
+ widest = spread;
10959
+ best = level;
10960
+ }
10961
+ }
10962
+ if (widest <= 0) {
10963
+ return range.Min;
10964
+ }
10965
+ this.thresholds.set(source, range.Min + best * step);
10966
+ return this.thresholds.get(source);
10967
+ });
10968
+ }
10969
+ /**
10970
+ * The level to measure against, read off the series when the caller names none.
10971
+ */
10972
+ Resolve(threshold, source = false) {
10973
+ return __awaiter(this, void 0, void 0, function* () {
10974
+ return threshold == null ? this.NaturalThreshold(source) : threshold;
10975
+ });
10976
+ }
10977
+ /**
10978
+ * The texels in one frame whose value is over a threshold.
10979
+ *
10980
+ * Ask for source to compare against the customer's own numbers, so a level stated against
10981
+ * their data means what they think it means.
10982
+ */
10983
+ MaskAbove(params) {
10984
+ return __awaiter(this, void 0, void 0, function* () {
10985
+ const options = params || {};
10986
+ const source = !!options.source;
10987
+ const frame = yield this.Frame(options.frame || 0, source);
10988
+ const level = yield this.Resolve(options.threshold, source);
10989
+ const bits = new Uint8Array(frame.Values.length);
10990
+ for (let at = 0; at < bits.length; at++) {
10991
+ bits[at] = frame.Covered[at] && frame.Values[at] > level ? 1 : 0;
10992
+ }
10993
+ const mask = { Width: frame.Width, Height: frame.Height, Bits: bits };
10994
+ if (options.excludeBaseline === false) {
10995
+ return mask;
10996
+ }
10997
+ const ignored = yield this.Ignored();
10998
+ return ignored ? Combine(mask, ignored, (left, right) => left && !right) : mask;
10999
+ });
11000
+ }
11001
+ /**
11002
+ * The texels over a threshold in every frame, or null for an archive with no frames.
11003
+ *
11004
+ * Measured rather than read off the first frame, so a tide that is out at the start does
11005
+ * not get counted as ground the event flooded.
11006
+ */
11007
+ AlwaysAbove(params) {
11008
+ return __awaiter(this, void 0, void 0, function* () {
11009
+ const options = params || {};
11010
+ let always = null;
11011
+ for (let index = 0; index < this.Length; index++) {
11012
+ const mask = yield this.MaskAbove({
11013
+ threshold: options.threshold,
11014
+ frame: index,
11015
+ excludeBaseline: options.excludeBaseline,
11016
+ source: options.source
11017
+ });
11018
+ always = always ? Combine(always, mask, (left, right) => left && right) : mask;
11019
+ }
11020
+ return always;
11021
+ });
11022
+ }
11023
+ /**
11024
+ * One frame's mask with anything below the noise floor already removed.
11025
+ *
11026
+ * Built from the components so the floor is applied once, which is what lets two frames be
11027
+ * compared as sets that already agree on what counts.
11028
+ */
11029
+ CoveredMask(params) {
11030
+ return __awaiter(this, void 0, void 0, function* () {
11031
+ const options = params || {};
11032
+ const mask = yield this.MaskAbove(options);
11033
+ const floor = options.minTexels == null ? ValueMapReader.MIN_TEXELS : options.minTexels;
11034
+ if (floor <= 1) {
11035
+ return mask;
11036
+ }
11037
+ const keep = new Uint8Array(mask.Bits.length);
11038
+ for (const members of Components(mask, floor)) {
11039
+ for (const texel of members) {
11040
+ keep[texel.Row * mask.Width + texel.Column] = 1;
11041
+ }
11042
+ }
11043
+ return { Width: mask.Width, Height: mask.Height, Bits: keep };
11044
+ });
11045
+ }
11046
+ /**
11047
+ * How many texels are over the threshold in each frame.
11048
+ *
11049
+ * Decoding is local, so the whole series is read without a request and the frames worth
11050
+ * querying can be found rather than guessed at.
11051
+ */
11052
+ Profile(params) {
11053
+ return __awaiter(this, void 0, void 0, function* () {
11054
+ const options = params || {};
11055
+ const floor = options.minTexels == null ? 1 : options.minTexels;
11056
+ const counts = [];
11057
+ for (let index = 0; index < this.Length; index++) {
11058
+ const mask = floor <= 1
11059
+ ? yield this.MaskAbove({
11060
+ threshold: options.threshold,
11061
+ frame: index,
11062
+ excludeBaseline: options.excludeBaseline,
11063
+ source: options.source
11064
+ })
11065
+ : yield this.CoveredMask({
11066
+ threshold: options.threshold,
11067
+ frame: index,
11068
+ minTexels: floor,
11069
+ excludeBaseline: options.excludeBaseline,
11070
+ source: options.source
11071
+ });
11072
+ counts.push(Count(mask));
11073
+ }
11074
+ return counts;
11075
+ });
11076
+ }
11077
+ /**
11078
+ * The frames that characterise an event: it arriving, its worst, and it passing.
11079
+ *
11080
+ * Read off the series rather than named by the caller, since a frame number that suits one
11081
+ * archive is quietly wrong for the next and finding the worst is usually the question.
11082
+ */
11083
+ FramesOfInterest(params) {
11084
+ return __awaiter(this, void 0, void 0, function* () {
11085
+ const options = params || {};
11086
+ const counts = yield this.Profile(options);
11087
+ if (!counts.length) {
11088
+ return [];
11089
+ }
11090
+ let peak = 0;
11091
+ for (let index = 1; index < counts.length; index++) {
11092
+ if (counts[index] > counts[peak]) {
11093
+ peak = index;
11094
+ }
11095
+ }
11096
+ if (!counts[peak]) {
11097
+ return [];
11098
+ }
11099
+ const limit = counts[peak] * (options.fraction == null ? ValueMapReader.ONSET_FRACTION : options.fraction);
11100
+ const over = [];
11101
+ counts.forEach((count, index) => {
11102
+ if (count >= limit) {
11103
+ over.push(index);
11104
+ }
11105
+ });
11106
+ // De-duplicated, since a short event can put all three on one frame.
11107
+ const picked = [over[0], peak, over[over.length - 1]];
11108
+ return picked.filter((at, index) => picked.indexOf(at) === index).sort((a, b) => a - b);
11109
+ });
11110
+ }
11111
+ /**
11112
+ * Each area of one frame over a threshold, as a Bruce geometry ready to query with.
11113
+ * Holes are dropped unless asked for.
11114
+ */
11115
+ ShapesAbove(params) {
11116
+ return __awaiter(this, void 0, void 0, function* () {
11117
+ const options = params || {};
11118
+ const mask = yield this.MaskAbove({
11119
+ threshold: options.threshold,
11120
+ frame: options.frame,
11121
+ excludeBaseline: options.excludeBaseline,
11122
+ source: options.source
11123
+ });
11124
+ return this.ShapesOf(mask, {
11125
+ minTexels: options.minTexels,
11126
+ keepHoles: options.keepHoles,
11127
+ tolerance: options.tolerance
11128
+ });
11129
+ });
11130
+ }
11131
+ /**
11132
+ * Each area of any mask as a Bruce geometry, for a caller who built the mask itself.
11133
+ */
11134
+ ShapesOf(mask, params) {
11135
+ const options = params || {};
11136
+ const floor = options.minTexels == null ? ValueMapReader.MIN_TEXELS : options.minTexels;
11137
+ const shapes = [];
11138
+ for (const members of Components(mask, floor)) {
11139
+ const rings = this.RingsOf(members, options.tolerance);
11140
+ if (rings.Outer.length < ValueMapReader.MIN_RING_POINTS) {
11141
+ continue;
11142
+ }
11143
+ shapes.push(Polygon(rings.Outer, options.keepHoles ? rings.Holes : []));
11144
+ }
11145
+ return shapes;
11146
+ }
11147
+ /**
11148
+ * One area's outer ring and its holes, as positions.
11149
+ *
11150
+ * The longest ring is the outer one, since a hole is enclosed by what surrounds it. Pass a
11151
+ * tolerance of 0 to keep every traced vertex.
11152
+ */
11153
+ RingsOf(members, tolerance) {
11154
+ const traced = Trace(members);
11155
+ if (!traced.length) {
11156
+ return { Outer: [], Holes: [] };
11157
+ }
11158
+ traced.sort((left, right) => right.length - left.length);
11159
+ const reduce = tolerance == null ? ValueMapReader.SIMPLIFY_TOLERANCE : tolerance;
11160
+ const outer = reduce ? Simplify(traced[0], reduce) : traced[0];
11161
+ const holes = traced.slice(1)
11162
+ .map((hole) => (reduce ? Simplify(hole, reduce) : hole))
11163
+ .filter((hole) => hole.length >= 4);
11164
+ return {
11165
+ Outer: this.Grid.RingToPoints(outer),
11166
+ Holes: holes.map((hole) => this.Grid.RingToPoints(hole))
11167
+ };
11168
+ }
11169
+ /**
11170
+ * Walks the series in order, reporting only the ground each frame adds.
11171
+ * Most frames add nothing, so onProgress is called for every frame while only the ones that grew come back.
11172
+ */
11173
+ NewAreas(params) {
11174
+ return __awaiter(this, void 0, void 0, function* () {
11175
+ const options = params || {};
11176
+ const times = this.Times;
11177
+ const added = [];
11178
+ let seen = null;
11179
+ for (let index = 0; index < this.Length; index++) {
11180
+ const mask = yield this.CoveredMask({
11181
+ threshold: options.threshold,
11182
+ frame: index,
11183
+ minTexels: options.minTexels,
11184
+ excludeBaseline: options.excludeBaseline,
11185
+ source: options.source
11186
+ });
11187
+ const fresh = seen ? Combine(mask, seen, (left, right) => left && !right) : mask;
11188
+ seen = seen ? Combine(seen, mask, (left, right) => left || right) : mask;
11189
+ // Each area here sits inside one that already cleared the floor, so applying the
11190
+ // floor again would drop the thin edge that makes this frame new.
11191
+ const shapes = Count(fresh)
11192
+ ? this.ShapesOf(fresh, { minTexels: 1, tolerance: options.tolerance })
11193
+ : [];
11194
+ if (options.onProgress) {
11195
+ options.onProgress({
11196
+ Frame: index,
11197
+ Frames: this.Length,
11198
+ Time: times[index] || null,
11199
+ NewTexels: Count(fresh),
11200
+ Added: !!shapes.length
11201
+ });
11202
+ }
11203
+ if (shapes.length) {
11204
+ added.push({
11205
+ Frame: index,
11206
+ Time: times[index] || null,
11207
+ NewTexels: Count(fresh),
11208
+ Shapes: shapes,
11209
+ New: fresh,
11210
+ Cumulative: { Width: seen.Width, Height: seen.Height, Bits: seen.Bits.slice() }
11211
+ });
11212
+ }
11213
+ }
11214
+ return added;
11215
+ });
11216
+ }
11217
+ /**
11218
+ * The texels an Entity covers, from where its record says it is.
11219
+ *
11220
+ * The raster does not move, so this holds for the whole series and a caller reading every
11221
+ * frame works it out once.
11222
+ *
11223
+ * Ask for geometry to use the Entity's own outline rather than the box around it.
11224
+ */
11225
+ TexelsOf(entity, useGeometry = false) {
11226
+ if (useGeometry) {
11227
+ const rings = RingsOfEntity(entity);
11228
+ if (rings.length) {
11229
+ const hits = new Map();
11230
+ for (const ring of rings) {
11231
+ for (const texel of this.Grid.TexelsUnder(ring)) {
11232
+ hits.set(texel.Row + ":" + texel.Column, texel);
11233
+ }
11234
+ }
11235
+ const found = [];
11236
+ hits.forEach((texel) => found.push(texel));
11237
+ return found.sort((left, right) => left.Row - right.Row || left.Column - right.Column);
11238
+ }
11239
+ }
11240
+ return this.Grid.TexelsIn(Bounds.FromEntity(entity));
11241
+ }
11242
+ /**
11243
+ * Every value a frame holds over a set of texels, skipping the texels it never sampled.
11244
+ *
11245
+ * An unsampled texel decodes to the bottom of the archive's range, which reads as a real
11246
+ * measurement rather than as an absence, so the coverage mask decides what is reported.
11247
+ */
11248
+ ReadingsAt(texels, frame) {
11249
+ const readings = [];
11250
+ for (const texel of texels || []) {
11251
+ const at = texel.Row * frame.Width + texel.Column;
11252
+ if (at < 0 || at >= frame.Covered.length || !frame.Covered[at]) {
11253
+ continue;
11254
+ }
11255
+ readings.push(frame.Values[at]);
11256
+ }
11257
+ return readings;
11258
+ }
11259
+ /**
11260
+ * The highest value a frame holds over a set of texels, or null where it holds nothing.
11261
+ *
11262
+ * The highest rather than the middle one, because something spanning more than one texel is
11263
+ * described by its worst part.
11264
+ */
11265
+ PeakAt(texels, frame = 0, source = false) {
11266
+ return __awaiter(this, void 0, void 0, function* () {
11267
+ const readings = this.ReadingsAt(texels, yield this.Frame(frame, source));
11268
+ return readings.length ? Math.max.apply(null, readings) : null;
11269
+ });
11270
+ }
11271
+ /**
11272
+ * The average value a frame holds over a set of texels, or null where it holds nothing.
11273
+ * What something spanning several texels experienced overall, as against the worst part.
11274
+ */
11275
+ MeanAt(texels, frame = 0, source = false) {
11276
+ return __awaiter(this, void 0, void 0, function* () {
11277
+ const readings = this.ReadingsAt(texels, yield this.Frame(frame, source));
11278
+ return readings.length ? Sum(readings) / readings.length : null;
11279
+ });
11280
+ }
11281
+ /**
11282
+ * How the values over a mask are spread, for a caller reporting on an area.
11283
+ */
11284
+ ValuesAt(mask, frame) {
11285
+ const selected = [];
11286
+ for (let at = 0; at < mask.Bits.length; at++) {
11287
+ if (mask.Bits[at] && frame.Covered[at]) {
11288
+ selected.push(frame.Values[at]);
11289
+ }
11290
+ }
11291
+ if (!selected.length) {
11292
+ return { Texels: 0 };
11293
+ }
11294
+ selected.sort((left, right) => left - right);
11295
+ const middle = Math.floor(selected.length / 2);
11296
+ return {
11297
+ Texels: selected.length,
11298
+ Peak: selected[selected.length - 1],
11299
+ Mean: Sum(selected) / selected.length,
11300
+ Median: selected.length % 2
11301
+ ? selected[middle]
11302
+ : (selected[middle - 1] + selected[middle]) / 2
11303
+ };
11304
+ }
11305
+ /**
11306
+ * The finest tile covering a position, or null when it falls outside every one of them.
11307
+ *
11308
+ * Tiles overlap, and the finer one is the better answer, so the last match wins over a list
11309
+ * that is already sorted coarse first.
11310
+ */
11311
+ TileAt(longitude, latitude) {
11312
+ let found = null;
11313
+ for (const tile of this.Tiles) {
11314
+ if (longitude >= tile.West && longitude <= tile.East
11315
+ && latitude >= tile.South && latitude <= tile.North) {
11316
+ found = tile;
11317
+ }
11318
+ }
11319
+ return found;
11320
+ }
11321
+ /**
11322
+ * One reading at one place, in both the delivered numbers and the source's own.
11323
+ *
11324
+ * Reads the single tile the position falls in rather than compositing the whole grid, which
11325
+ * is what makes charting one point over a long series cheap.
11326
+ */
11327
+ SampleAt(longitude, latitude, frame = 0) {
11328
+ return __awaiter(this, void 0, void 0, function* () {
11329
+ const tile = this.TileAt(longitude, latitude);
11330
+ if (!tile) {
11331
+ return null;
11332
+ }
11333
+ const position = this.position(frame);
11334
+ const times = this.Times;
11335
+ const reading = {
11336
+ Longitude: longitude,
11337
+ Latitude: latitude,
11338
+ Frame: position,
11339
+ Time: times[position] || null,
11340
+ Units: this.Units,
11341
+ Texel: this.Grid.TexelAt(longitude, latitude),
11342
+ Covered: false,
11343
+ Value: null,
11344
+ SourceValue: null
11345
+ };
11346
+ const at = TexelIn(tile, longitude, latitude);
11347
+ if (!at) {
11348
+ return reading;
11349
+ }
11350
+ const index = this.Tiles.indexOf(tile);
11351
+ const values = yield this.tileLayer(index, ValueMapMetadata.LAYER_VALUE, position);
11352
+ if (!values || !values.Values) {
11353
+ return reading;
11354
+ }
11355
+ const cell = at.Row * values.Width + at.Column;
11356
+ if (!values.Covered[cell]) {
11357
+ return reading;
11358
+ }
11359
+ const value = values.Values[cell];
11360
+ reading.Covered = true;
11361
+ reading.Value = value;
11362
+ reading.SourceValue = value;
11363
+ if (this.Transform) {
11364
+ const shift = yield this.tileLayer(index, ValueMapMetadata.LAYER_VALUE_SHIFT);
11365
+ if (!shift || !shift.Values) {
11366
+ throw new Error(this.missingShift());
11367
+ }
11368
+ reading.SourceValue = shift.Covered[cell] ? value - shift.Values[cell] : null;
11369
+ }
11370
+ return reading;
11371
+ });
11372
+ }
11373
+ /**
11374
+ * Everything the archive knows about one place, for a caller inspecting a single point.
11375
+ */
11376
+ DetailsAt(longitude, latitude, frame = 0) {
11377
+ return __awaiter(this, void 0, void 0, function* () {
11378
+ const reading = yield this.SampleAt(longitude, latitude, frame);
11379
+ if (!reading) {
11380
+ return null;
11381
+ }
11382
+ const tile = this.TileAt(longitude, latitude);
11383
+ const at = TexelIn(tile, longitude, latitude);
11384
+ const position = this.Tiles.indexOf(tile);
11385
+ return Object.assign({}, reading, {
11386
+ Attribute: this.Attribute,
11387
+ Label: this.Label,
11388
+ Dataset: this.Dataset,
11389
+ Frames: this.Length,
11390
+ Transform: this.Transform || null,
11391
+ VerticalDatum: this.VerticalDatum,
11392
+ Tile: {
11393
+ Level: tile.Level,
11394
+ TexelMetres: tile.TexelMetres,
11395
+ ResolutionX: tile.ResolutionX,
11396
+ ResolutionY: tile.ResolutionY,
11397
+ West: tile.West,
11398
+ East: tile.East,
11399
+ South: tile.South,
11400
+ North: tile.North
11401
+ },
11402
+ TileTexel: at,
11403
+ ValueMin: yield this.tileReading(position, ValueMapMetadata.LAYER_VALUE_MIN, at),
11404
+ ValueMax: yield this.tileReading(position, ValueMapMetadata.LAYER_VALUE_MAX, at),
11405
+ Shift: yield this.tileReading(position, ValueMapMetadata.LAYER_VALUE_SHIFT, at),
11406
+ Baseline: yield this.tileFlag(position, ValueMapMetadata.LAYER_BASELINE, at)
11407
+ });
11408
+ });
11409
+ }
11410
+ /**
11411
+ * Every reading at one place over the whole series, in the order the times run.
11412
+ */
11413
+ SeriesAt(longitude, latitude) {
11414
+ return __awaiter(this, void 0, void 0, function* () {
11415
+ const tile = this.TileAt(longitude, latitude);
11416
+ if (!tile) {
11417
+ return [];
11418
+ }
11419
+ const at = TexelIn(tile, longitude, latitude);
11420
+ const times = this.Times;
11421
+ const position = this.Tiles.indexOf(tile);
11422
+ const frames = yield this.tileSeries(position, ValueMapMetadata.LAYER_VALUE);
11423
+ const shift = this.Transform
11424
+ ? yield this.tileLayer(position, ValueMapMetadata.LAYER_VALUE_SHIFT)
11425
+ : null;
11426
+ if (this.Transform && (!shift || !shift.Values)) {
11427
+ throw new Error(this.missingShift());
11428
+ }
11429
+ return times.map((time, index) => {
11430
+ const reading = {
11431
+ Longitude: longitude,
11432
+ Latitude: latitude,
11433
+ Frame: index,
11434
+ Time: time,
11435
+ Units: this.Units,
11436
+ Texel: this.Grid.TexelAt(longitude, latitude),
11437
+ Covered: false,
11438
+ Value: null,
11439
+ SourceValue: null
11440
+ };
11441
+ const field = frames[index];
11442
+ if (!at || !field || !field.Values) {
11443
+ return reading;
11444
+ }
11445
+ const cell = at.Row * field.Width + at.Column;
11446
+ if (!field.Covered[cell]) {
11447
+ return reading;
11448
+ }
11449
+ reading.Covered = true;
11450
+ reading.Value = field.Values[cell];
11451
+ reading.SourceValue = field.Values[cell];
11452
+ if (shift) {
11453
+ reading.SourceValue = shift.Covered[cell]
11454
+ ? field.Values[cell] - shift.Values[cell]
11455
+ : null;
11456
+ }
11457
+ return reading;
11458
+ });
11459
+ });
11460
+ }
11461
+ /**
11462
+ * What one frame holds over an Entity, in both the delivered numbers and the source's own.
11463
+ */
11464
+ SampleOf(entity, frame = 0, useGeometry = false) {
11465
+ return __awaiter(this, void 0, void 0, function* () {
11466
+ const texels = this.TexelsOf(entity, useGeometry);
11467
+ const times = this.Times;
11468
+ const position = frame >= 0 ? frame : times.length + frame;
11469
+ const summary = {
11470
+ Frame: position,
11471
+ Time: times[position] || null,
11472
+ Units: this.Units,
11473
+ Texels: texels.length,
11474
+ Covered: 0,
11475
+ Peak: null,
11476
+ Mean: null,
11477
+ SourcePeak: null,
11478
+ SourceMean: null
11479
+ };
11480
+ if (!texels.length) {
11481
+ return summary;
11482
+ }
11483
+ const readings = this.ReadingsAt(texels, yield this.Frame(position));
11484
+ summary.Covered = readings.length;
11485
+ if (readings.length) {
11486
+ summary.Peak = Math.max.apply(null, readings);
11487
+ summary.Mean = Sum(readings) / readings.length;
11488
+ }
11489
+ if (!this.Transform) {
11490
+ summary.SourcePeak = summary.Peak;
11491
+ summary.SourceMean = summary.Mean;
11492
+ return summary;
11493
+ }
11494
+ const sourceReadings = this.ReadingsAt(texels, yield this.Frame(position, true));
11495
+ if (sourceReadings.length) {
11496
+ summary.SourcePeak = Math.max.apply(null, sourceReadings);
11497
+ summary.SourceMean = Sum(sourceReadings) / sourceReadings.length;
11498
+ }
11499
+ return summary;
11500
+ });
11501
+ }
11502
+ /**
11503
+ * What every frame holds over one Entity, for a caller charting a record through an event.
11504
+ */
11505
+ SeriesOf(entity, useGeometry = false) {
11506
+ return __awaiter(this, void 0, void 0, function* () {
11507
+ const summaries = [];
11508
+ for (let index = 0; index < this.Length; index++) {
11509
+ summaries.push(yield this.SampleOf(entity, index, useGeometry));
11510
+ }
11511
+ return summaries;
11512
+ });
11513
+ }
11514
+ identity() {
11515
+ return this.Metadata.Identity || {};
11516
+ }
11517
+ /*
11518
+ * A frame index checked against the series, with a negative one counted from the end.
11519
+ */
11520
+ position(index) {
11521
+ const frames = this.Length;
11522
+ if (!frames) {
11523
+ throw new Error("This value map has no frames.");
11524
+ }
11525
+ const at = index >= 0 ? index : frames + index;
11526
+ if (at < 0 || at >= frames) {
11527
+ throw new Error("Frame " + index + " is outside the " + frames
11528
+ + " this value map holds.");
11529
+ }
11530
+ return at;
11531
+ }
11532
+ /*
11533
+ * One per-texel statistic over the whole series, in the archive's own units, or null.
11534
+ */
11535
+ statistic(name) {
11536
+ return __awaiter(this, void 0, void 0, function* () {
11537
+ if (this.statistics.has(name)) {
11538
+ return this.statistics.get(name);
11539
+ }
11540
+ const field = this.Layer(name) ? yield this.composite(name) : null;
11541
+ this.statistics.set(name, field);
11542
+ return field;
11543
+ });
11544
+ }
11545
+ /*
11546
+ * One layer drawn onto the composite grid from every tile that carries it.
11547
+ *
11548
+ * Nearest neighbour throughout: a texel is one mesh sample, and interpolating it with its
11549
+ * neighbour produces a reading no cell ever reported.
11550
+ */
11551
+ composite(name, index) {
11552
+ return __awaiter(this, void 0, void 0, function* () {
11553
+ const width = this.Grid.Width;
11554
+ const height = this.Grid.Height;
11555
+ const isMask = this.packing(name) === ValueMapMetadata.FORMAT_MASK;
11556
+ const values = isMask ? null : new Float64Array(width * height);
11557
+ const covered = new Uint8Array(width * height);
11558
+ const ranges = this.Tiles.map((tile) => ValueMapMetadata.BytesOf(tile, name, index));
11559
+ const rasters = yield this.decodeAll(ranges);
11560
+ let drawn = false;
11561
+ for (let at = 0; at < this.Tiles.length; at++) {
11562
+ const raster = rasters[at];
11563
+ if (!raster) {
11564
+ continue;
11565
+ }
11566
+ drawn = true;
11567
+ this.paste(this.Tiles[at], raster, name, values, covered);
11568
+ }
11569
+ if (!drawn) {
11570
+ return null;
11571
+ }
11572
+ return { Width: width, Height: height, Values: values, Covered: covered };
11573
+ });
11574
+ }
11575
+ /*
11576
+ * One tile's pixels written onto the composite grid, unpacked as they land.
11577
+ */
11578
+ paste(tile, raster, name, values, covered) {
11579
+ const rect = ValueMapGrid.TileRect(tile, this.Extent, { Width: this.Grid.Width, Height: this.Grid.Height });
11580
+ const range = this.range(name);
11581
+ const format = this.packing(name);
11582
+ const width = this.Grid.Width;
11583
+ const height = this.Grid.Height;
11584
+ for (let row = 0; row < rect.Height; row++) {
11585
+ const target = rect.Top + row;
11586
+ if (target < 0 || target >= height) {
11587
+ continue;
11588
+ }
11589
+ const sourceRow = Math.min(raster.Height - 1, Math.floor((row + 0.5) * raster.Height / rect.Height));
11590
+ for (let column = 0; column < rect.Width; column++) {
11591
+ const across = rect.Left + column;
11592
+ if (across < 0 || across >= width) {
11593
+ continue;
11594
+ }
11595
+ const sourceColumn = Math.min(raster.Width - 1, Math.floor((column + 0.5) * raster.Width / rect.Width));
11596
+ const from = (sourceRow * raster.Width + sourceColumn) * 4;
11597
+ const to = target * width + across;
11598
+ if (raster.Data[from + 3] <= ValueMapMetadata.NO_DATA) {
11599
+ covered[to] = 0;
11600
+ if (values) {
11601
+ values[to] = 0;
11602
+ }
11603
+ continue;
11604
+ }
11605
+ covered[to] = 1;
11606
+ if (values) {
11607
+ values[to] = ValueMapMetadata.Decode(ValueMapMetadata.PackedAt(raster.Data, from, format), format, range);
11608
+ }
11609
+ }
11610
+ }
11611
+ }
11612
+ /*
11613
+ * One tile's copy of a layer, unpacked, or null when that tile does not carry it.
11614
+ *
11615
+ * Kept per tile rather than composited, since a point sits in one tile and building the
11616
+ * whole grid to read a single texel costs one image decode per tile in the archive.
11617
+ */
11618
+ tileLayer(position, name, index) {
11619
+ return __awaiter(this, void 0, void 0, function* () {
11620
+ const key = position + ":" + name + ":" + (index == null ? "" : index);
11621
+ if (this.tileLayers.has(key)) {
11622
+ const held = this.tileLayers.get(key);
11623
+ this.tileLayers.delete(key);
11624
+ this.tileLayers.set(key, held);
11625
+ return held;
11626
+ }
11627
+ const at = ValueMapMetadata.BytesOf(this.Tiles[position], name, index);
11628
+ const rasters = yield this.decodeAll([at]);
11629
+ const raster = rasters[0];
11630
+ const field = raster
11631
+ ? Unpack(raster, this.packing(name), this.range(name))
11632
+ : null;
11633
+ this.tileLayers.set(key, field);
11634
+ Trim(this.tileLayers, ValueMapReader.TILE_CACHE);
11635
+ return field;
11636
+ });
11637
+ }
11638
+ /*
11639
+ * One texel's number in a named layer of one tile, or null where the layer does not cover it.
11640
+ */
11641
+ tileReading(position, name, at) {
11642
+ return __awaiter(this, void 0, void 0, function* () {
11643
+ if (!at || !this.Layer(name)) {
11644
+ return null;
11645
+ }
11646
+ const field = yield this.tileLayer(position, name);
11647
+ if (!field || !field.Values) {
11648
+ return null;
11649
+ }
11650
+ const cell = at.Row * field.Width + at.Column;
11651
+ return field.Covered[cell] ? field.Values[cell] : null;
11652
+ });
11653
+ }
11654
+ /*
11655
+ * Whether a mask layer flags one texel of one tile.
11656
+ */
11657
+ tileFlag(position, name, at) {
11658
+ return __awaiter(this, void 0, void 0, function* () {
11659
+ if (!at || !this.Layer(name)) {
11660
+ return false;
11661
+ }
11662
+ const field = yield this.tileLayer(position, name);
11663
+ if (!field) {
11664
+ return false;
11665
+ }
11666
+ return !!field.Covered[at.Row * field.Width + at.Column];
11667
+ });
11668
+ }
11669
+ /*
11670
+ * One tile's whole per-time series, unpacked, in one read of the blob.
11671
+ *
11672
+ * Kept out of the tile cache on purpose: a long series would evict everything else in it,
11673
+ * and the caller walking a chart holds the result anyway.
11674
+ */
11675
+ tileSeries(position, name) {
11676
+ return __awaiter(this, void 0, void 0, function* () {
11677
+ const tile = this.Tiles[position];
11678
+ const ranges = this.Times.map((_time, index) => ValueMapMetadata.BytesOf(tile, name, index));
11679
+ const rasters = yield this.decodeAll(ranges);
11680
+ const format = this.packing(name);
11681
+ const range = this.range(name);
11682
+ return rasters.map((raster) => raster ? Unpack(raster, format, range) : null);
11683
+ });
11684
+ }
11685
+ /*
11686
+ * Every named slice decoded, over as few reads of the blob as the layout allows.
11687
+ */
11688
+ decodeAll(ranges) {
11689
+ return __awaiter(this, void 0, void 0, function* () {
11690
+ const reads = ValueMapMetadata.PlanReads(ranges);
11691
+ if (!reads.length) {
11692
+ return ranges.map(() => null);
11693
+ }
11694
+ const blocks = yield Promise.all(reads.map((read) => this.bytes(read.Offset, read.Length)));
11695
+ return Promise.all(ranges.map((at) => {
11696
+ if (!at || !at.Length) {
11697
+ return Promise.resolve(null);
11698
+ }
11699
+ for (let index = 0; index < reads.length; index++) {
11700
+ const read = reads[index];
11701
+ if (at.Offset < read.Offset || at.Offset + at.Length > read.Offset + read.Length) {
11702
+ continue;
11703
+ }
11704
+ const from = at.Offset - read.Offset;
11705
+ return this.decoder(blocks[index].subarray(from, from + at.Length))
11706
+ .catch(() => null);
11707
+ }
11708
+ return Promise.resolve(null);
11709
+ }));
11710
+ });
11711
+ }
11712
+ /*
11713
+ * How a named layer is packed, since a statistics layer can differ from the frames.
11714
+ */
11715
+ packing(name) {
11716
+ const layer = this.Layer(name);
11717
+ return (layer && layer.Format) || this.Format;
11718
+ }
11719
+ /*
11720
+ * The range a named layer was quantised over, falling back to the frames' own.
11721
+ */
11722
+ range(name) {
11723
+ return ValueMapMetadata.LayerRange(this.Metadata, name) || this.ValueRange;
11724
+ }
11725
+ /*
11726
+ * A frame's delivered values as the source's own numbers.
11727
+ *
11728
+ * Nothing to undo is the common case and is not an error: an archive that declares no
11729
+ * transform delivered the source's numbers untouched.
11730
+ */
11731
+ toSource(frame) {
11732
+ return __awaiter(this, void 0, void 0, function* () {
11733
+ if (!this.Transform) {
11734
+ return frame;
11735
+ }
11736
+ const shift = yield this.ValueShift();
11737
+ if (!shift || !shift.Values) {
11738
+ throw new Error(this.missingShift());
11739
+ }
11740
+ const values = new Float64Array(frame.Values.length);
11741
+ const covered = new Uint8Array(frame.Covered.length);
11742
+ for (let at = 0; at < values.length; at++) {
11743
+ // A texel the shift never sampled has no source reading to report, whatever the
11744
+ // frame holds.
11745
+ covered[at] = frame.Covered[at] && shift.Covered[at] ? 1 : 0;
11746
+ values[at] = frame.Values[at] - (shift.Covered[at] ? shift.Values[at] : 0);
11747
+ }
11748
+ return { Width: frame.Width, Height: frame.Height, Values: values, Covered: covered };
11749
+ });
11750
+ }
11751
+ missingShift() {
11752
+ return "This value map declares a " + this.Transform + " transform but publishes no "
11753
+ + ValueMapMetadata.LAYER_VALUE_SHIFT + " layer, so the source's own numbers cannot be"
11754
+ + " recovered.";
11755
+ }
11756
+ }
11757
+ ValueMapReader.Reader = Reader;
11758
+ function Open(params) {
11759
+ return __awaiter(this, void 0, void 0, function* () {
11760
+ let file = params.file;
11761
+ if (!file) {
11762
+ if (!params.fileId) {
11763
+ throw new Error("A Client File, or a Client File ID, is required.");
11764
+ }
11765
+ const found = yield ClientFile.Get({ api: params.api, fileId: params.fileId });
11766
+ file = found ? found.clientFile : null;
11767
+ }
11768
+ if (!file) {
11769
+ throw new Error("Client File " + params.fileId + " was not found.");
11770
+ }
11771
+ return new Reader({
11772
+ file: file,
11773
+ bytes: params.bytes || SourceFor(file, params.blob, params.api),
11774
+ decoder: params.decoder,
11775
+ maxCompositeTexels: params.maxCompositeTexels,
11776
+ extent: params.extent
11777
+ });
11778
+ });
11779
+ }
11780
+ ValueMapReader.Open = Open;
11781
+ /**
11782
+ * Where an archive's bytes come from: a blob already held, or ranged reads of the file's URL.
11783
+ */
11784
+ function SourceFor(file, blob, api) {
11785
+ if (blob) {
11786
+ const held = blob instanceof Uint8Array ? blob : new Uint8Array(blob);
11787
+ return (offset, length) => Promise.resolve(held.subarray(offset, offset + length));
11788
+ }
11789
+ // The record's own URL where it carries one, since it may be a signed or CDN address that
11790
+ // a reconstructed one would not match.
11791
+ const url = file.URL || ClientFile.GetUrl({ api: api, fileId: file.ID });
11792
+ return (offset, length) => __awaiter(this, void 0, void 0, function* () {
11793
+ const response = yield fetch(url, {
11794
+ headers: { Range: "bytes=" + offset + "-" + (offset + length - 1) }
11795
+ });
11796
+ return new Uint8Array(yield response.arrayBuffer());
11797
+ });
11798
+ }
11799
+ ValueMapReader.SourceFor = SourceFor;
11800
+ /**
11801
+ * Decodes a PNG where the runtime has an image decoder of its own.
11802
+ *
11803
+ * A browser has one. Node does not, and rather than pull an image library into a client library
11804
+ * that mostly does not decode anything, a caller there passes their own.
11805
+ */
11806
+ ValueMapReader.DefaultDecoder = (bytes) => __awaiter(this, void 0, void 0, function* () {
11807
+ const globals = typeof globalThis !== "undefined" ? globalThis : {};
11808
+ if (typeof globals.createImageBitmap !== "function" || typeof globals.OffscreenCanvas !== "function") {
11809
+ throw new Error("This runtime has no image decoder, so a value map's pixels cannot be"
11810
+ + " read here. Pass Decoder to read them, or use the metadata surface, which needs"
11811
+ + " no decoding.");
11812
+ }
11813
+ const blob = new globals.Blob([bytes], { type: "image/png" });
11814
+ const bitmap = yield globals.createImageBitmap(blob);
11815
+ try {
11816
+ const canvas = new globals.OffscreenCanvas(bitmap.width, bitmap.height);
11817
+ const context = canvas.getContext("2d");
11818
+ context.drawImage(bitmap, 0, 0);
11819
+ const pixels = context.getImageData(0, 0, bitmap.width, bitmap.height);
11820
+ return { Width: bitmap.width, Height: bitmap.height, Data: pixels.data };
11821
+ }
11822
+ finally {
11823
+ if (bitmap.close) {
11824
+ bitmap.close();
11825
+ }
11826
+ }
11827
+ });
11828
+ // A ring needs three distinct corners and the repeat that closes it.
11829
+ ValueMapReader.MIN_RING_POINTS = 4;
11830
+ /**
11831
+ * The disconnected areas in a mask, as 4-connected groups of texels.
11832
+ */
11833
+ function Components(mask, minTexels = 1) {
11834
+ const width = mask.Width;
11835
+ const height = mask.Height;
11836
+ const seen = new Uint8Array(mask.Bits.length);
11837
+ const found = [];
11838
+ for (let start = 0; start < mask.Bits.length; start++) {
11839
+ if (!mask.Bits[start] || seen[start]) {
11840
+ continue;
11841
+ }
11842
+ seen[start] = 1;
11843
+ const pending = [start];
11844
+ const members = [];
11845
+ while (pending.length) {
11846
+ const at = pending.pop();
11847
+ const row = Math.floor(at / width);
11848
+ const column = at - row * width;
11849
+ members.push({ Row: row, Column: column });
11850
+ if (row > 0) {
11851
+ Visit(mask, seen, pending, at - width);
11852
+ }
11853
+ if (row < height - 1) {
11854
+ Visit(mask, seen, pending, at + width);
9898
11855
  }
9899
- else if (geo.Polygon) {
9900
- merged.Polygon = geo.Polygon;
11856
+ if (column > 0) {
11857
+ Visit(mask, seen, pending, at - 1);
9901
11858
  }
9902
- };
9903
- full.MultiGeometry.forEach(addToMerged);
9904
- return {
9905
- geometry: merged,
9906
- properties: properties
9907
- };
11859
+ if (column < width - 1) {
11860
+ Visit(mask, seen, pending, at + 1);
11861
+ }
11862
+ }
11863
+ if (members.length >= minTexels) {
11864
+ found.push(members);
11865
+ }
9908
11866
  }
9909
- // If there are many polygons but only 1 line and/or 1 point then we'll put those singles into the top-level.
9910
- // Eg: 5 polygons, 1 point, 1 line. That should be a single geometry.
9911
- if (countsPerType.Polygon > 1 && (countsPerType.LineString <= 1 && countsPerType.Point <= 1)) {
9912
- const semiMerged = {};
9913
- const addToSemiMerged = (geo) => {
9914
- if (geo.Point) {
9915
- semiMerged.Point = geo.Point;
11867
+ return found;
11868
+ }
11869
+ ValueMapReader.Components = Components;
11870
+ /**
11871
+ * The closed rings bounding a group of texels, in texel corner coordinates.
11872
+ */
11873
+ function Trace(members) {
11874
+ const held = new Set();
11875
+ for (const texel of members) {
11876
+ held.add(texel.Row + ":" + texel.Column);
11877
+ }
11878
+ const edges = new Map();
11879
+ const emit = (from, to) => {
11880
+ const outgoing = edges.get(from);
11881
+ if (outgoing) {
11882
+ outgoing.push(to);
11883
+ }
11884
+ else {
11885
+ edges.set(from, [to]);
11886
+ }
11887
+ };
11888
+ const corner = (x, y) => x + ":" + y;
11889
+ for (const texel of members) {
11890
+ const row = texel.Row;
11891
+ const column = texel.Column;
11892
+ if (!held.has((row - 1) + ":" + column)) {
11893
+ emit(corner(column, row), corner(column + 1, row));
11894
+ }
11895
+ if (!held.has(row + ":" + (column + 1))) {
11896
+ emit(corner(column + 1, row), corner(column + 1, row + 1));
11897
+ }
11898
+ if (!held.has((row + 1) + ":" + column)) {
11899
+ emit(corner(column + 1, row + 1), corner(column, row + 1));
11900
+ }
11901
+ if (!held.has(row + ":" + (column - 1))) {
11902
+ emit(corner(column, row + 1), corner(column, row));
11903
+ }
11904
+ }
11905
+ const rings = [];
11906
+ const starts = [];
11907
+ edges.forEach((_outgoing, from) => starts.push(from));
11908
+ for (const start of starts) {
11909
+ while ((edges.get(start) || []).length) {
11910
+ const ring = [start];
11911
+ let node = edges.get(start).pop();
11912
+ let closed = true;
11913
+ while (node !== start) {
11914
+ ring.push(node);
11915
+ const outgoing = edges.get(node);
11916
+ if (!outgoing || !outgoing.length) {
11917
+ closed = false;
11918
+ break;
11919
+ }
11920
+ node = outgoing.pop();
9916
11921
  }
9917
- else if (geo.LineString) {
9918
- semiMerged.LineString = geo.LineString;
11922
+ if (closed) {
11923
+ ring.push(start);
11924
+ rings.push(ring.map(ToCorner));
9919
11925
  }
9920
- };
9921
- semiMerged.MultiGeometry = full.MultiGeometry.filter(geo => geo.Polygon);
9922
- full.MultiGeometry.forEach(addToSemiMerged);
9923
- return {
9924
- geometry: semiMerged,
9925
- properties: properties
9926
- };
11926
+ }
9927
11927
  }
9928
- // More than 1 of each, then return the full multi-geometry as-is.
9929
- return {
9930
- geometry: full,
9931
- properties: properties
9932
- };
11928
+ return rings;
9933
11929
  }
9934
- Geometry.FromGeoJson = FromGeoJson;
9935
- })(Geometry || (Geometry = {}));
9936
-
9937
- var Bounds;
9938
- (function (Bounds) {
11930
+ ValueMapReader.Trace = Trace;
9939
11931
  /**
9940
- * Calculates boundaries from entity.
9941
- * This will not use the entity's boundaries and instead calculate from geometry and location.
9942
- * @param entity
11932
+ * A ring with its texel staircase reduced, by Ramer-Douglas-Peucker.
9943
11933
  */
9944
- function FromEntity(entity) {
9945
- const bounds = {
9946
- maxAltitude: null,
9947
- maxLatitude: null,
9948
- maxLongitude: null,
9949
- minAltitude: null,
9950
- minLatitude: null,
9951
- minLongitude: null
9952
- };
9953
- const points = [];
9954
- const assemblyLocation = Entity.GetValue({
9955
- entity: entity,
9956
- path: ["Bruce", "AssemblyLocation"]
9957
- });
9958
- if (assemblyLocation) {
9959
- points.push(assemblyLocation);
11934
+ function Simplify(ring, tolerance) {
11935
+ const points = ring.slice();
11936
+ if (tolerance <= 0 || points.length < 4) {
11937
+ return points;
9960
11938
  }
9961
- const location = Entity.GetValue({
9962
- entity: entity,
9963
- path: ["Bruce", "Location"]
9964
- });
9965
- if (location) {
9966
- points.push(location);
11939
+ // Split before reducing: the algorithm anchors on the endpoints, and a closed ring's two
11940
+ // endpoints are the same point, which would anchor on nothing.
11941
+ const half = Math.floor(points.length / 2);
11942
+ const front = Reduce(points.slice(0, half + 1), tolerance);
11943
+ const back = Reduce(points.slice(half), tolerance);
11944
+ const reduced = front.slice(0, front.length - 1).concat(back);
11945
+ return reduced.length >= 4 ? reduced : points;
11946
+ }
11947
+ ValueMapReader.Simplify = Simplify;
11948
+ /**
11949
+ * How many texels a mask holds.
11950
+ */
11951
+ function Count(mask) {
11952
+ let total = 0;
11953
+ for (let at = 0; at < mask.Bits.length; at++) {
11954
+ total += mask.Bits[at] ? 1 : 0;
9967
11955
  }
9968
- const geometry = Entity.GetValue({
9969
- entity: entity,
9970
- path: ["Bruce", "VectorGeometry"]
9971
- });
9972
- const processGeometry = (geometry, depth = 0) => {
9973
- if (!geometry || depth > 5) {
9974
- return;
11956
+ return total;
11957
+ }
11958
+ ValueMapReader.Count = Count;
11959
+ /**
11960
+ * Two masks merged texel by texel.
11961
+ */
11962
+ function Combine(left, right, rule) {
11963
+ const bits = new Uint8Array(left.Bits.length);
11964
+ for (let at = 0; at < bits.length; at++) {
11965
+ bits[at] = rule(!!left.Bits[at], !!right.Bits[at]) ? 1 : 0;
11966
+ }
11967
+ return { Width: left.Width, Height: left.Height, Bits: bits };
11968
+ }
11969
+ ValueMapReader.Combine = Combine;
11970
+ /**
11971
+ * A polygon geometry from an outer ring and its holes, ready to query Bruce with.
11972
+ */
11973
+ function Polygon(outer, holes) {
11974
+ const toCartos = (ring) => ring.map((point) => ({
11975
+ longitude: point.Longitude,
11976
+ latitude: point.Latitude,
11977
+ altitude: 0
11978
+ }));
11979
+ const rings = [{
11980
+ LinearRing: Geometry.LineStrFromPoints(toCartos(outer)),
11981
+ Facing: Geometry.EPolygonRingType.Boundaries
11982
+ }];
11983
+ for (const hole of holes || []) {
11984
+ rings.push({
11985
+ LinearRing: Geometry.LineStrFromPoints(toCartos(hole)),
11986
+ Facing: Geometry.EPolygonRingType.Hole
11987
+ });
11988
+ }
11989
+ return { Polygon: rings };
11990
+ }
11991
+ ValueMapReader.Polygon = Polygon;
11992
+ /**
11993
+ * An Entity's polygon rings, or nothing when its record carries no outline.
11994
+ */
11995
+ function RingsOfEntity(entity) {
11996
+ const source = entity && entity.Bruce ? entity.Bruce : entity;
11997
+ const geometry = (source && source.VectorGeometry) || {};
11998
+ const rings = [];
11999
+ for (const face of geometry.Polygon || []) {
12000
+ if (!face || !face.LinearRing) {
12001
+ continue;
9975
12002
  }
9976
- if (geometry.Point) {
9977
- points.push(...Geometry.ParsePoints(geometry.Point));
12003
+ const points = Geometry.ParsePoints(face.LinearRing);
12004
+ if (points.length >= 3) {
12005
+ rings.push(points.map((point) => ({
12006
+ Longitude: point.longitude,
12007
+ Latitude: point.latitude
12008
+ })));
9978
12009
  }
9979
- if (geometry.LineString) {
9980
- points.push(...Geometry.ParsePoints(geometry.LineString));
12010
+ }
12011
+ return rings;
12012
+ }
12013
+ ValueMapReader.RingsOfEntity = RingsOfEntity;
12014
+ /*
12015
+ * One decoded raster as its numbers and its own coverage, at the raster's own size.
12016
+ */
12017
+ function Unpack(raster, format, range) {
12018
+ const isMask = format === ValueMapMetadata.FORMAT_MASK;
12019
+ const values = isMask ? null : new Float64Array(raster.Width * raster.Height);
12020
+ const covered = new Uint8Array(raster.Width * raster.Height);
12021
+ for (let cell = 0; cell < covered.length; cell++) {
12022
+ const from = cell * 4;
12023
+ if (raster.Data[from + 3] <= ValueMapMetadata.NO_DATA) {
12024
+ continue;
9981
12025
  }
9982
- if (geometry.Polygon) {
9983
- for (const ring of geometry.Polygon) {
9984
- points.push(...Geometry.ParsePoints(ring.LinearRing));
9985
- }
12026
+ covered[cell] = 1;
12027
+ if (values) {
12028
+ values[cell] = ValueMapMetadata.Decode(ValueMapMetadata.PackedAt(raster.Data, from, format), format, range);
9986
12029
  }
9987
- if (geometry.MultiGeometry) {
9988
- for (const multiGeometry of geometry.MultiGeometry) {
9989
- processGeometry(multiGeometry, depth + 1);
9990
- }
12030
+ }
12031
+ return { Width: raster.Width, Height: raster.Height, Values: values, Covered: covered };
12032
+ }
12033
+ /*
12034
+ * One neighbour queued, when it is in the mask and has not been reached yet.
12035
+ */
12036
+ function Visit(mask, seen, pending, at) {
12037
+ if (mask.Bits[at] && !seen[at]) {
12038
+ seen[at] = 1;
12039
+ pending.push(at);
12040
+ }
12041
+ }
12042
+ /*
12043
+ * One run of a ring reduced against the line between its ends.
12044
+ */
12045
+ function Reduce(run, tolerance) {
12046
+ if (run.length < 3) {
12047
+ return run;
12048
+ }
12049
+ const first = run[0];
12050
+ const last = run[run.length - 1];
12051
+ const dx = last.X - first.X;
12052
+ const dy = last.Y - first.Y;
12053
+ const span = Math.sqrt(dx * dx + dy * dy);
12054
+ let worstIndex = 0;
12055
+ let worst = -1;
12056
+ for (let index = 1; index < run.length - 1; index++) {
12057
+ const point = run[index];
12058
+ const distance = span === 0
12059
+ ? Math.sqrt(Math.pow(point.X - first.X, 2) + Math.pow(point.Y - first.Y, 2))
12060
+ : Math.abs(dy * point.X - dx * point.Y + last.X * first.Y - last.Y * first.X) / span;
12061
+ if (distance > worst) {
12062
+ worst = distance;
12063
+ worstIndex = index;
12064
+ }
12065
+ }
12066
+ if (worst <= tolerance) {
12067
+ return [first, last];
12068
+ }
12069
+ const front = Reduce(run.slice(0, worstIndex + 1), tolerance);
12070
+ return front.slice(0, front.length - 1).concat(Reduce(run.slice(worstIndex), tolerance));
12071
+ }
12072
+ /*
12073
+ * A corner key back as its coordinates.
12074
+ */
12075
+ function ToCorner(key) {
12076
+ const parts = key.split(":");
12077
+ return { X: Number(parts[0]), Y: Number(parts[1]) };
12078
+ }
12079
+ /*
12080
+ * The population standard deviation of a set of counts.
12081
+ */
12082
+ function Deviation(counts) {
12083
+ if (!counts.length) {
12084
+ return 0;
12085
+ }
12086
+ const mean = Sum(counts) / counts.length;
12087
+ let total = 0;
12088
+ for (const count of counts) {
12089
+ total += Math.pow(count - mean, 2);
12090
+ }
12091
+ return Math.sqrt(total / counts.length);
12092
+ }
12093
+ function Sum(values) {
12094
+ let total = 0;
12095
+ for (const value of values) {
12096
+ total += value;
12097
+ }
12098
+ return total;
12099
+ }
12100
+ /*
12101
+ * Where a position lands inside one tile's own raster.
12102
+ */
12103
+ function TexelIn(tile, longitude, latitude) {
12104
+ if (tile.East <= tile.West || tile.North <= tile.South) {
12105
+ return null;
12106
+ }
12107
+ const width = tile.ResolutionX;
12108
+ const height = tile.ResolutionY;
12109
+ const column = Math.max(0, Math.min(width - 1, Math.floor((longitude - tile.West) / (tile.East - tile.West) * width)));
12110
+ const row = Math.max(0, Math.min(height - 1, Math.floor((tile.North - latitude) / (tile.North - tile.South) * height)));
12111
+ return { Row: row, Column: column };
12112
+ }
12113
+ /*
12114
+ * The oldest entries dropped once a cache is over its limit.
12115
+ */
12116
+ function Trim(cache, limit) {
12117
+ while (cache.size > limit) {
12118
+ const oldest = cache.keys().next();
12119
+ if (oldest.done) {
12120
+ return;
9991
12121
  }
12122
+ cache.delete(oldest.value);
12123
+ }
12124
+ }
12125
+ })(ValueMapReader || (ValueMapReader = {}));
12126
+
12127
+ /**
12128
+ * Generated value maps: what one declares, how to find it, and how to read it.
12129
+ *
12130
+ * A value map is a tiled raster of one attribute over a geographic area at a series of times, for
12131
+ * example water depth over a region hour by hour.
12132
+ *
12133
+ * The archive is one blob of PNG tiles with a byte offset and length per layer per tile,
12134
+ * and the block described below is the only account of what those bytes mean.
12135
+ */
12136
+ var ClientFileValueMap;
12137
+ (function (ClientFileValueMap) {
12138
+ const VALUE_MAP_SOURCE_PATH = "generation/Identity/EntityTypeSourceID";
12139
+ const VALUE_MAP_ATTRIBUTE_PATH = "generation/Identity/Attribute";
12140
+ const VALUE_MAP_ENTITY_TYPE_PATH = "generation/Identity/EntityTypeID";
12141
+ const VALUE_MAP_DATASET_PATH = "generation/Identity/Dataset";
12142
+ const VALUE_MAP_PAGE_SIZE = 300;
12143
+ /*
12144
+ * A query part as the list of values it narrows to.
12145
+ */
12146
+ function valueMapValues(value) {
12147
+ if (value == null) {
12148
+ return [];
12149
+ }
12150
+ return (Array.isArray(value) ? value : [value]).map((at) => String(at));
12151
+ }
12152
+ /*
12153
+ * One Data path condition. Comma separated values become an SQL IN on the API side.
12154
+ */
12155
+ function valueMapRow(path, values) {
12156
+ return {
12157
+ column: ClientFile.Filter.EColumn.Data,
12158
+ path: path,
12159
+ rowOperator: ClientFile.Filter.ERowOperator.AND,
12160
+ value: values.join(","),
12161
+ includes: true,
12162
+ valueOperator: values.length > 1 ? "in" : "equals",
12163
+ rows: []
9992
12164
  };
9993
- processGeometry(geometry);
9994
- for (const point of points) {
9995
- if (bounds.maxLatitude == null || point.latitude > bounds.maxLatitude) {
9996
- bounds.maxLatitude = point.latitude;
12165
+ }
12166
+ /*
12167
+ * Whether a file's published Identity satisfies every part of a query that was given.
12168
+ */
12169
+ function matchesValueMapQuery(file, query) {
12170
+ var _a, _b;
12171
+ const identity = (_b = (_a = file === null || file === void 0 ? void 0 : file.Data) === null || _a === void 0 ? void 0 : _a.generation) === null || _b === void 0 ? void 0 : _b.Identity;
12172
+ if (!identity) {
12173
+ return false;
12174
+ }
12175
+ const sources = valueMapValues(query.entityTypeSourceId);
12176
+ // A source ID reaches us as a number from the API and as a string from a filter echo.
12177
+ if (sources.length && sources.indexOf(String(identity.EntityTypeSourceID)) < 0) {
12178
+ return false;
12179
+ }
12180
+ const attributes = valueMapValues(query.attribute);
12181
+ if (attributes.length && attributes.indexOf(String(identity.Attribute)) < 0) {
12182
+ return false;
12183
+ }
12184
+ if (query.entityTypeId && String(identity.EntityTypeID) !== String(query.entityTypeId)) {
12185
+ return false;
12186
+ }
12187
+ if (query.dataset && String(identity.Dataset) !== String(query.dataset)) {
12188
+ return false;
12189
+ }
12190
+ return true;
12191
+ }
12192
+ /**
12193
+ * The generated value maps matching a query, newest first.
12194
+ */
12195
+ function GetList(params) {
12196
+ return __awaiter(this, void 0, void 0, function* () {
12197
+ let { api, req: reqParams } = params;
12198
+ if (!api) {
12199
+ api = ENVIRONMENT.Api().GetBruceApi();
9997
12200
  }
9998
- if (bounds.maxLongitude == null || point.longitude > bounds.maxLongitude) {
9999
- bounds.maxLongitude = point.longitude;
12201
+ const query = {
12202
+ entityTypeSourceId: params.entityTypeSourceId,
12203
+ attribute: params.attribute,
12204
+ entityTypeId: params.entityTypeId,
12205
+ dataset: params.dataset
12206
+ };
12207
+ const sources = valueMapValues(query.entityTypeSourceId);
12208
+ const attributes = valueMapValues(query.attribute);
12209
+ const rows = [];
12210
+ if (sources.length) {
12211
+ rows.push(valueMapRow(VALUE_MAP_SOURCE_PATH, sources));
10000
12212
  }
10001
- if (bounds.maxAltitude == null || point.altitude > bounds.maxAltitude) {
10002
- bounds.maxAltitude = point.altitude;
12213
+ if (attributes.length) {
12214
+ rows.push(valueMapRow(VALUE_MAP_ATTRIBUTE_PATH, attributes));
10003
12215
  }
10004
- if (bounds.minLatitude == null || point.latitude < bounds.minLatitude) {
10005
- bounds.minLatitude = point.latitude;
12216
+ if (query.entityTypeId) {
12217
+ rows.push(valueMapRow(VALUE_MAP_ENTITY_TYPE_PATH, [query.entityTypeId]));
10006
12218
  }
10007
- if (bounds.minLongitude == null || point.longitude < bounds.minLongitude) {
10008
- bounds.minLongitude = point.longitude;
12219
+ if (query.dataset) {
12220
+ rows.push(valueMapRow(VALUE_MAP_DATASET_PATH, [query.dataset]));
10009
12221
  }
10010
- if (bounds.minAltitude == null || point.altitude < bounds.minAltitude) {
10011
- bounds.minAltitude = point.altitude;
12222
+ if (!rows.length) {
12223
+ throw new Error("A value map query needs at least one of an Entity Type Source ID, an"
12224
+ + " attribute, an Entity Type ID or a dataset, since every generated file would"
12225
+ + " otherwise match.");
10012
12226
  }
10013
- }
10014
- return bounds;
12227
+ const key = "client-file-value-map-list-" + JSON.stringify(query);
12228
+ const cache = api.GetCacheItem(key, reqParams);
12229
+ if (cache === null || cache === void 0 ? void 0 : cache.found) {
12230
+ return cache.data;
12231
+ }
12232
+ const prom = (() => __awaiter(this, void 0, void 0, function* () {
12233
+ const res = yield ClientFile.GetList({
12234
+ api,
12235
+ filter: {
12236
+ PageSize: params.pageSize || VALUE_MAP_PAGE_SIZE,
12237
+ PageIndex: 0,
12238
+ FilterRows: rows
12239
+ },
12240
+ req: reqParams
12241
+ });
12242
+ const generatedAt = (file) => { var _a, _b, _c; return String(((_c = (_b = (_a = file === null || file === void 0 ? void 0 : file.Data) === null || _a === void 0 ? void 0 : _a.generation) === null || _b === void 0 ? void 0 : _b.Identity) === null || _c === void 0 ? void 0 : _c.Generated) || ""); };
12243
+ const revisionOf = (file) => { var _a, _b, _c; return Number(((_c = (_b = (_a = file === null || file === void 0 ? void 0 : file.Data) === null || _a === void 0 ? void 0 : _a.generation) === null || _b === void 0 ? void 0 : _b.Identity) === null || _c === void 0 ? void 0 : _c.Revision) || 0); };
12244
+ const sorted = (res.clientFiles || [])
12245
+ .filter((file) => matchesValueMapQuery(file, query))
12246
+ .sort((left, right) => {
12247
+ const byTime = generatedAt(right).localeCompare(generatedAt(left));
12248
+ return byTime !== 0 ? byTime : revisionOf(right) - revisionOf(left);
12249
+ });
12250
+ return {
12251
+ files: sorted
12252
+ };
12253
+ }))();
12254
+ api.SetCacheItem({
12255
+ key,
12256
+ value: prom,
12257
+ req: reqParams
12258
+ });
12259
+ return prom;
12260
+ });
10015
12261
  }
10016
- Bounds.FromEntity = FromEntity;
10017
- })(Bounds || (Bounds = {}));
12262
+ ClientFileValueMap.GetList = GetList;
12263
+ /**
12264
+ * The newest generated value map matching a query, or null.
12265
+ */
12266
+ function Get(params) {
12267
+ return __awaiter(this, void 0, void 0, function* () {
12268
+ const { files } = yield GetList(params);
12269
+ return { file: files.length ? files[0] : null };
12270
+ });
12271
+ }
12272
+ ClientFileValueMap.Get = Get;
12273
+ // Reading what a published block declares, with no bytes fetched and no decoder needed.
12274
+ ClientFileValueMap.Metadata = ValueMapMetadata;
12275
+ // Placing the raster on the world: texels, extents, rings and the composite arithmetic.
12276
+ ClientFileValueMap.Grid = ValueMapGrid;
12277
+ /**
12278
+ * A value map read from a Client File. See ValueMapReader.Open.
12279
+ */
12280
+ function Open(params) {
12281
+ return ValueMapReader.Open(params);
12282
+ }
12283
+ ClientFileValueMap.Open = Open;
12284
+ /**
12285
+ * Where an archive's bytes come from. See ValueMapReader.SourceFor.
12286
+ */
12287
+ function SourceFor(file, blob, api) {
12288
+ return ValueMapReader.SourceFor(file, blob, api);
12289
+ }
12290
+ ClientFileValueMap.SourceFor = SourceFor;
12291
+ /**
12292
+ * Decodes a PNG where the runtime has an image decoder of its own.
12293
+ */
12294
+ function DefaultDecoder(bytes) {
12295
+ return ValueMapReader.DefaultDecoder(bytes);
12296
+ }
12297
+ ClientFileValueMap.DefaultDecoder = DefaultDecoder;
12298
+ /**
12299
+ * One archive's reader, for a caller holding a record and its bytes already.
12300
+ */
12301
+ function NewReader(params) {
12302
+ return new ValueMapReader.Reader(params);
12303
+ }
12304
+ ClientFileValueMap.NewReader = NewReader;
12305
+ })(ClientFileValueMap || (ClientFileValueMap = {}));
10018
12306
 
10019
12307
  var Camera;
10020
12308
  (function (Camera) {
@@ -15062,6 +17350,24 @@ var ExportCsv;
15062
17350
  }
15063
17351
  })(ExportCsv || (ExportCsv = {}));
15064
17352
 
17353
+ var ExportNsx;
17354
+ (function (ExportNsx) {
17355
+ /**
17356
+ * Starts a whole-account NSX backup export job.
17357
+ * The generated NSX file is stored in the account's configured backup file store.
17358
+ */
17359
+ function AccountBackup(params) {
17360
+ return __awaiter(this, void 0, void 0, function* () {
17361
+ let { api, req: reqParams } = params !== null && params !== void 0 ? params : {};
17362
+ if (!api) {
17363
+ api = ENVIRONMENT.Api().GetBruceApi();
17364
+ }
17365
+ return api.POST("export/nsx/accountBackup", {}, Api.PrepReqParams(reqParams));
17366
+ });
17367
+ }
17368
+ ExportNsx.AccountBackup = AccountBackup;
17369
+ })(ExportNsx || (ExportNsx = {}));
17370
+
15065
17371
  var ExportUsd;
15066
17372
  (function (ExportUsd) {
15067
17373
  function Export(params) {
@@ -20955,7 +23261,7 @@ var UrlUtils;
20955
23261
  })(UrlUtils || (UrlUtils = {}));
20956
23262
 
20957
23263
  // This is updated with the package.json version on build.
20958
- const VERSION = "7.1.87";
23264
+ const VERSION = "7.1.89";
20959
23265
 
20960
- export { VERSION, Account, AccountAudit, AccountConcept, AccountFeatures, AccountInvite, AccountLimits, AccountTemplate, AccountType, AnnDocument, AbstractApi, Api, ApiGetters, BruceApi, GlobalApi, GuardianApi, Assembly, Calculator, ChangeSet, ClientFile, Bounds, BruceEvent, BruceVariable, CacheControl, Camera, Cartes, Carto, Color, DelayQueue, GeoJson, Geometry, LRUCache, UTC, CustomForm, DashboardView, DataFeed, DataLab, DataLabGroup, DataSource, DataTransform, Comment, Entity, EntityAttachment, EntityAttachmentType, EntityAttribute, EntityComment, EntityCoords, EntityHistoricData, EntityLink, EntityLod, EntityLodCategory, EntityRelation, EntityRelationType, EntitySource, EntityTableView, EntityTag, EntityType, EntityTypeRelation, EntityTypeTrigger, Ontology, OntologyDocument, ENVIRONMENT, ExportBrz, ExportCsv, ExportUsd, Hexbin, ImportAssembly, ImportCad, ImportCsv, ImportGeoJson, ImportJson, ImportKml, ImportLcc, ImportedFile, Uploader, Markup, UIMarkup, NAVIGATOR_CHAT_EVENT_ENTITY_HIGHLIGHT_APPLIED, NAVIGATOR_CHAT_EVENT_SCENE_CONTEXT_PREFETCHED, NavigatorChatClient, NavigatorMcpWebSocketClient, Plugin, PluginAiTool, ProgramKey, MenuItem, ProjectView, ProjectViewBookmark, ProjectViewBookmarkGroup, ProjectViewLegacy, ProjectViewLegacyBookmark, ProjectViewLegacyTile, ProjectViewTile, ZoomControl, Scenario, HostingLocation, MessageBroker, PendingAction, RecordChangeFeed, Style, Tileset, Tracking, Permission, Session, User, UserGroup, UserMfaMethod, EncryptUtils, MathUtils, ObjectUtils, PathUtils, UrlUtils };
23266
+ export { VERSION, Account, AccountAudit, AccountConcept, AccountFeatures, AccountInvite, AccountLimits, AccountTemplate, AccountType, AnnDocument, AbstractApi, Api, ApiGetters, BruceApi, GlobalApi, GuardianApi, Assembly, Calculator, ChangeSet, ClientFile, ClientFileValueMap, Bounds, BruceEvent, BruceVariable, CacheControl, Camera, Cartes, Carto, Color, DelayQueue, GeoJson, Geometry, LRUCache, UTC, CustomForm, DashboardView, DataFeed, DataLab, DataLabGroup, DataSource, DataTransform, Comment, Entity, EntityAttachment, EntityAttachmentType, EntityAttribute, EntityComment, EntityCoords, EntityHistoricData, EntityLink, EntityLod, EntityLodCategory, EntityRelation, EntityRelationType, EntitySource, EntityTableView, EntityTag, EntityType, EntityTypeRelation, EntityTypeTrigger, Ontology, OntologyDocument, ENVIRONMENT, ExportBrz, ExportCsv, ExportNsx, ExportUsd, Hexbin, ImportAssembly, ImportCad, ImportCsv, ImportGeoJson, ImportJson, ImportKml, ImportLcc, ImportedFile, Uploader, Markup, UIMarkup, NAVIGATOR_CHAT_EVENT_ENTITY_HIGHLIGHT_APPLIED, NAVIGATOR_CHAT_EVENT_SCENE_CONTEXT_PREFETCHED, NavigatorChatClient, NavigatorMcpWebSocketClient, Plugin, PluginAiTool, ProgramKey, MenuItem, ProjectView, ProjectViewBookmark, ProjectViewBookmarkGroup, ProjectViewLegacy, ProjectViewLegacyBookmark, ProjectViewLegacyTile, ProjectViewTile, ZoomControl, Scenario, HostingLocation, MessageBroker, PendingAction, RecordChangeFeed, Style, Tileset, Tracking, Permission, Session, User, UserGroup, UserMfaMethod, EncryptUtils, MathUtils, ObjectUtils, PathUtils, UrlUtils };
20961
23267
  //# sourceMappingURL=bruce-models.es5.js.map