@dra2020/baseclient 1.0.171 → 1.0.173

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.
@@ -5,6 +5,15 @@ import * as Q from './quad';
5
5
  export type Topo = any;
6
6
  export declare function topoFromCollection(col: any): Topo;
7
7
  export declare function topoContiguity(topo: Topo): any;
8
+ /** Hits are geometry that would otherwise have been rebuilt from arcs; misses are the rebuilds that
9
+ * were actually necessary. That distinction is the point - a raw conversion count cannot tell them
10
+ * apart. Call as Poly.topoFeatureCacheStats() from the console. */
11
+ export declare function topoFeatureCacheStats(): {
12
+ hits: number;
13
+ misses: number;
14
+ pct: number;
15
+ };
16
+ export declare function topoFeatureCacheReset(): void;
8
17
  export declare function topoToFeature(topo: Topo, geoid: string): any;
9
18
  export type TopoSpliceEntry = {
10
19
  topology: Topo;
package/lib/poly/topo.ts CHANGED
@@ -105,9 +105,67 @@ export function topoContiguity(topo: Topo): any
105
105
  return result;
106
106
  }
107
107
 
108
+ /**
109
+ * Resolved polygon geometry, keyed on the TOPOLOGY OBJECT and held at module level.
110
+ *
111
+ * The two things a topology object stands for have opposite natures, and the split matters:
112
+ *
113
+ * ARCS are a property of the topology the object appears in. The same object shares different arcs
114
+ * in different topologies - a precinct and a block do not break at the same places - which is why
115
+ * arc offsets live in a per-topology WeakMap (topology.packed.objectArcs) rather than on the object.
116
+ *
117
+ * GEOMETRY is not. The points are the same wherever the object appears. It may end up PACKED into
118
+ * different buffers depending on which collection packed it, but the coordinates are identical, and
119
+ * a collection packed from several buffers is fine by construction (see packCollection).
120
+ *
121
+ * So geometry can be cached once, globally, for the life of the object. Module level rather than a
122
+ * field on the object precisely to avoid the project-then-strip dance that packedarcs still needs
123
+ * around JSON.stringify in topoToBuffer - a trick that has produced real bugs by leaving values
124
+ * persisted where they should not be. A WeakMap is invisible to serialization and dies with the
125
+ * object.
126
+ *
127
+ * THE RULE THAT KEEPS THIS CORRECT. Any operation that changes an object's resolved geometry must
128
+ * produce a NEW object; operations that preserve geometry may share identity. Both hold today: splice
129
+ * renumbers and subdivides arcs, neither of which moves a point, and it now shares objects
130
+ * deliberately; simplification goes through topoFromCollection and so builds new objects. DRA
131
+ * topologies are unquantized, so there is no transform that could decode the same arcs differently.
132
+ */
133
+ const builtGeometry = new WeakMap<any, any>();
134
+ let cacheHits = 0;
135
+ let cacheMisses = 0;
136
+
137
+ /** Hits are geometry that would otherwise have been rebuilt from arcs; misses are the rebuilds that
138
+ * were actually necessary. That distinction is the point - a raw conversion count cannot tell them
139
+ * apart. Call as Poly.topoFeatureCacheStats() from the console. */
140
+ export function topoFeatureCacheStats(): { hits: number, misses: number, pct: number }
141
+ {
142
+ const total = cacheHits + cacheMisses;
143
+ return { hits: cacheHits, misses: cacheMisses, pct: total ? Math.round((cacheHits / total) * 100) : 0 };
144
+ }
145
+
146
+ export function topoFeatureCacheReset(): void { cacheHits = 0; cacheMisses = 0 }
147
+
108
148
  export function topoToFeature(topo: Topo, geoid: string): any
109
149
  {
110
- return correctGeometry(TopoClient.feature(topo, topo.objects[geoid]));
150
+ const o: any = topo.objects[geoid];
151
+ const geometry = o == null ? undefined : builtGeometry.get(o);
152
+ if (geometry !== undefined)
153
+ {
154
+ cacheHits++;
155
+ // A fresh Feature wrapper each call, matching what TopoClient.feature returns - including sharing
156
+ // the properties object with the topology object, which it already did. Only the geometry, the
157
+ // expensive and immutable part, is reused.
158
+ const f: any = { type: 'Feature', properties: o.properties == null ? {} : o.properties, geometry };
159
+ if (o.id != null) f.id = o.id;
160
+ if (o.bbox != null) f.bbox = o.bbox;
161
+ return f;
162
+ }
163
+ cacheMisses++;
164
+ const built = correctGeometry(TopoClient.feature(topo, o));
165
+ // Cached AFTER correctGeometry, so every later reader gets the rewound rings rather than repeating
166
+ // the correction.
167
+ if (o != null && built && built.geometry) builtGeometry.set(o, built.geometry);
168
+ return built;
111
169
  }
112
170
 
113
171
  export type TopoSpliceEntry = { topology: Topo, filterout?: any };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dra2020/baseclient",
3
- "version": "1.0.171",
3
+ "version": "1.0.173",
4
4
  "description": "Utility functions for Javascript projects.",
5
5
  "main": "dist/baseclient.js",
6
6
  "types": "./dist/all/all.d.ts",
@@ -44,9 +44,9 @@
44
44
  "webpack-cli": "^5.1.4"
45
45
  },
46
46
  "dependencies": {
47
- "@dra2020/topojson-client": "^3.2.17",
47
+ "@dra2020/topojson-client": "^3.2.18",
48
48
  "@dra2020/topojson-server": "^3.0.103",
49
- "@dra2020/topojson-simplify": "^3.0.102",
49
+ "@dra2020/topojson-simplify": "^3.0.103",
50
50
  "diff-match-patch": "^1.0.5",
51
51
  "geojson": "^0.5.0",
52
52
  "object-hash": "^3.0.0",