maplibre-tilepackage-protocol 1.1.0 → 1.2.1
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.
- package/README.md +116 -48
- package/dist/bundle.js +5657 -0
- package/dist/bundle.js.map +1 -0
- package/dist/index.esm.js +2060 -0
- package/dist/index.esm.js.map +1 -0
- package/eslint.config.mjs +28 -0
- package/package.json +42 -22
- package/rollup.config.mjs +45 -0
- package/src/default-decompress.js +11 -4
- package/src/get-header-and-filelist.js +42 -3
- package/src/index.js +175 -103
- package/src/maplibre-gl-js-protocol.js +28 -12
- package/src/shared-promise-cache.js +23 -0
- package/src/source.js +19 -4
- package/src/tilecutter/IMPLEMENTATION_PLAN.md +135 -0
- package/src/tilecutter/subdivide.js +247 -0
|
@@ -80,6 +80,7 @@ export default class SharedPromiseCache {
|
|
|
80
80
|
this.maxCacheEntries = maxCacheEntries;
|
|
81
81
|
this.counter = 1;
|
|
82
82
|
//this.decompress = decompress;
|
|
83
|
+
this.subdivided = new Map(); // key: sourceKey|z|x|y -> Uint8Array
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
async getHeader(source) {
|
|
@@ -174,6 +175,28 @@ export default class SharedPromiseCache {
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
getSubdivided(source, z, x, y) {
|
|
179
|
+
const key = `${source.getKey()}|${z}|${x}|${y}|Subdivided`;
|
|
180
|
+
return this.subdivided.get(key);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setSubdivided(source, z, x, y, bytes) {
|
|
184
|
+
const key = `${source.getKey()}|${z}|${x}|${y}|Subdivided`;
|
|
185
|
+
if (!this.subdivided.has(key)) {
|
|
186
|
+
this.subdivided.set(key, bytes);
|
|
187
|
+
// Optional lightweight prune if huge:
|
|
188
|
+
if (this.subdivided.size > this.maxCacheEntries * 2) {
|
|
189
|
+
// Remove oldest half (no usage tracking kept; iterate arbitrary)
|
|
190
|
+
let toRemove = Math.floor(this.subdivided.size / 2);
|
|
191
|
+
for (const k of this.subdivided.keys()) {
|
|
192
|
+
this.subdivided.delete(k);
|
|
193
|
+
toRemove--;
|
|
194
|
+
if (toRemove <= 0) break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
177
200
|
async invalidate(source) {
|
|
178
201
|
const key = source.getKey();
|
|
179
202
|
if (this.invalidations.get(key)) {
|
package/src/source.js
CHANGED
|
@@ -18,9 +18,12 @@ export class Source {
|
|
|
18
18
|
* see https://developer.mozilla.org/en-US/docs/Web/API/File_API
|
|
19
19
|
*/
|
|
20
20
|
export class FileSource extends Source {
|
|
21
|
-
constructor(file) {
|
|
21
|
+
constructor(file, options) {
|
|
22
22
|
super();
|
|
23
23
|
this.file = file;
|
|
24
|
+
if (options && options.coverageCheck) {
|
|
25
|
+
this.coverageCheck = options.coverageCheck;
|
|
26
|
+
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
getKey() {
|
|
@@ -48,14 +51,23 @@ export class FileSource extends Source {
|
|
|
48
51
|
* This also works around browser and storage-specific edge cases.
|
|
49
52
|
*/
|
|
50
53
|
export class FetchSource extends Source {
|
|
51
|
-
constructor(url, customHeaders = new Headers()) {
|
|
54
|
+
constructor(url, options, customHeaders = new Headers()) {
|
|
52
55
|
super();
|
|
53
56
|
this.url = url;
|
|
54
57
|
this.customHeaders = customHeaders;
|
|
55
58
|
this.mustReload = false;
|
|
59
|
+
// Safe global detection for legacy environments without globalThis
|
|
60
|
+
const g =
|
|
61
|
+
typeof globalThis !== "undefined"
|
|
62
|
+
? globalThis
|
|
63
|
+
: typeof window !== "undefined"
|
|
64
|
+
? window
|
|
65
|
+
: typeof self !== "undefined"
|
|
66
|
+
? self
|
|
67
|
+
: {};
|
|
56
68
|
let userAgent = "";
|
|
57
|
-
if (
|
|
58
|
-
userAgent =
|
|
69
|
+
if (g && typeof g.navigator !== "undefined") {
|
|
70
|
+
userAgent = g.navigator.userAgent || "";
|
|
59
71
|
}
|
|
60
72
|
const isWindows = userAgent.indexOf("Windows") > -1;
|
|
61
73
|
const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent);
|
|
@@ -63,6 +75,9 @@ export class FetchSource extends Source {
|
|
|
63
75
|
if (isWindows && isChromiumBased) {
|
|
64
76
|
this.chromeWindowsNoCache = true;
|
|
65
77
|
}
|
|
78
|
+
if (options && options.coverageCheck) {
|
|
79
|
+
this.coverageCheck = options.coverageCheck;
|
|
80
|
+
}
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
getKey() {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Vector Tile Subdivision (Tilecutter) – Current Status & Future Work
|
|
2
|
+
|
|
3
|
+
## Implemented (Current State)
|
|
4
|
+
|
|
5
|
+
The following pieces are complete and in production code:
|
|
6
|
+
|
|
7
|
+
- `subdivideVectorTile(...)` Phase A implementation using existing decode (`@mapbox/vector-tile`) and encode (`@maplibre/vt-pbf`) libraries.
|
|
8
|
+
- Scale/offset math for arbitrary `dz = targetZ - parentZ` with containment validation.
|
|
9
|
+
- Geometry transformation and clipping (buffer default 128) for points, lines, polygons.
|
|
10
|
+
- Layer filtering via `includeLayers` (array or RegExp).
|
|
11
|
+
- Stats returned (`featuresIn`, `featuresOut`, `dz`, `ms`).
|
|
12
|
+
- Automatic parent selection via `coverageMap` (value=1) and subdivision fallback in `TilePackage.getZxyAttempt`.
|
|
13
|
+
- In‑memory cache of synthesized tiles (`SharedPromiseCache.subdivided`) with simple half-prune heuristic.
|
|
14
|
+
- Default `coverageCheck` enabled and propagated to both `FetchSource` and `FileSource`.
|
|
15
|
+
- Debug instrumentation removed (warnings only for failures & high dz).
|
|
16
|
+
|
|
17
|
+
## Deferred / Not Yet Implemented
|
|
18
|
+
|
|
19
|
+
The items below represent remaining or future enhancements. They supersede earlier phased plan language.
|
|
20
|
+
|
|
21
|
+
1. Unit Tests
|
|
22
|
+
|
|
23
|
+
- Synthetic fixtures for point, line, polygon, multi-dz (e.g. dz=1,2,3).
|
|
24
|
+
- Verify clipping correctness (edge touch, full outside, polygon ring closure).
|
|
25
|
+
- Regression tests for parent containment validation and empty output tiles.
|
|
26
|
+
|
|
27
|
+
2. Benchmarks
|
|
28
|
+
|
|
29
|
+
- Measure average subdivision latency for typical dz values (1–5) and publish a README table.
|
|
30
|
+
- Track memory footprint of cached tiles.
|
|
31
|
+
|
|
32
|
+
3. Cache Improvements
|
|
33
|
+
|
|
34
|
+
- Replace simple prune with true LRU (track lastUsed for subdivided tiles).
|
|
35
|
+
- Optional byte-size cap (e.g. max 64 MB) + eviction on overflow.
|
|
36
|
+
- Expose cache stats (hits, misses, evictions) via an API or debug callback.
|
|
37
|
+
|
|
38
|
+
4. Max dz Guard
|
|
39
|
+
|
|
40
|
+
- Configurable hard cap `maxDz` (skip synthesis beyond threshold; return empty tile or parent tile).
|
|
41
|
+
- Distinct from warning-only `maxDzWarn` currently in function.
|
|
42
|
+
|
|
43
|
+
5. Dependency Reduction (Minimal MVT Codec)
|
|
44
|
+
|
|
45
|
+
- Custom lightweight decoder: parse layers, feature type, properties, geometry command stream.
|
|
46
|
+
- Custom encoder for POINT, LINESTRING, POLYGON; property passthrough.
|
|
47
|
+
- Goal: shrink bundle size and reduce external dependencies.
|
|
48
|
+
|
|
49
|
+
6. Geometry Simplification (High dz)
|
|
50
|
+
|
|
51
|
+
- Optional per-layer simplification tolerance parameter (Douglas-Peucker or grid-based).
|
|
52
|
+
- Preserve topology for polygons; avoid slivers.
|
|
53
|
+
|
|
54
|
+
7. Synthetic Metadata & Flags
|
|
55
|
+
|
|
56
|
+
- Option to embed a small custom layer or feature attribute indicating `parentZ`/`dz` for diagnostics.
|
|
57
|
+
- API return could include `synthetic: true` marker (currently implicit via stats presence).
|
|
58
|
+
|
|
59
|
+
8. Parent Search Strategy Options
|
|
60
|
+
|
|
61
|
+
- Fallback mode that uses nearest ancestor even if value != 1 (for packages lacking proper coverage semantics).
|
|
62
|
+
- Configurable allowlist of coverage values (e.g. treat `1` or `"blob"` as valid parents).
|
|
63
|
+
|
|
64
|
+
9. Observability & Metrics
|
|
65
|
+
|
|
66
|
+
- Pluggable callback `onSubdivide(stats, context)`.
|
|
67
|
+
- Accumulate moving averages; expose via `TilePackage.getSubdivisionMetrics()`.
|
|
68
|
+
|
|
69
|
+
10. Memory & Concurrency Controls
|
|
70
|
+
|
|
71
|
+
- Limit parallel subdivision operations (queue) to avoid main-thread spikes.
|
|
72
|
+
- Optional Web Worker offload (future) for heavy dz.
|
|
73
|
+
|
|
74
|
+
11. Error Handling Extensions
|
|
75
|
+
|
|
76
|
+
- Graceful fallback: on subdivision failure, return blank tile vs warning.
|
|
77
|
+
- Configurable retry for transient decode issues.
|
|
78
|
+
|
|
79
|
+
12. Documentation Enhancements
|
|
80
|
+
|
|
81
|
+
- README section: “Overzoom Mechanics & Tradeoffs”.
|
|
82
|
+
- Guidance on choosing `maxDz` based on feature density.
|
|
83
|
+
|
|
84
|
+
13. Security / Validation
|
|
85
|
+
|
|
86
|
+
- Defensive checks on malformed PBF (guard against extremely large extents or geometry counts).
|
|
87
|
+
|
|
88
|
+
14. Feature Count / Size Limits
|
|
89
|
+
|
|
90
|
+
- Option `maxFeaturesOut` to truncate layers—report truncation in stats.
|
|
91
|
+
|
|
92
|
+
15. Layer-Specific Options
|
|
93
|
+
|
|
94
|
+
- Per-layer buffer overrides; per-layer simplification tolerance.
|
|
95
|
+
|
|
96
|
+
## Updated API Roadmap (Proposed Additions)
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
interface SubdivideOptions {
|
|
100
|
+
buffer?: number; // existing
|
|
101
|
+
includeLayers?: string[] | RegExp; // existing
|
|
102
|
+
maxDzWarn?: number; // existing
|
|
103
|
+
maxDz?: number; // NEW hard cap
|
|
104
|
+
simplifyTolerance?: number; // NEW (all layers)
|
|
105
|
+
onStats?: (stats, ctx) => void; // NEW callback
|
|
106
|
+
maxFeaturesOut?: number; // NEW truncation limit
|
|
107
|
+
parentStrategy?: "coverageValue" | "nearestAncestor"; // NEW
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Prioritization Suggestion (Next Steps)
|
|
112
|
+
|
|
113
|
+
1. Implement unit tests (foundation for safe refactors).
|
|
114
|
+
2. Add LRU cache + metrics collection.
|
|
115
|
+
3. Introduce `maxDz` guard & benchmark to inform default.
|
|
116
|
+
4. Optional: minimal codec prototype behind feature flag for size comparisons.
|
|
117
|
+
5. Geometry simplification + truncation limits for high-density datasets.
|
|
118
|
+
6. Parent strategy option for archives with unconventional coverage values.
|
|
119
|
+
|
|
120
|
+
## Risks & Mitigations (Current)
|
|
121
|
+
|
|
122
|
+
- High dz latency: mitigate with `maxDz` and caching.
|
|
123
|
+
- Memory growth from cache: LRU + size cap.
|
|
124
|
+
- Bundle size: address via minimal codec.
|
|
125
|
+
- Incorrect clipping for edge cases: comprehensive unit tests.
|
|
126
|
+
|
|
127
|
+
## Open Questions
|
|
128
|
+
|
|
129
|
+
- Should synthetic tiles preserve empty layers (current: omit empty)?
|
|
130
|
+
- Provide global toggle to disable stats collection for minimal overhead?
|
|
131
|
+
- Standardize metadata flag naming (`vtpk_synthetic`?) if added.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
This document now reflects current implementation and focuses on forward-looking tasks. Completed exploratory/phase details were removed for clarity.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// Phase A: Subdivide a parent vector tile into a higher zoom target tile.
|
|
2
|
+
// Minimal dependency implementation using @mapbox/vector-tile for decode and @maplibre/vt-pbf for encode.
|
|
3
|
+
// Public API: subdivideVectorTile(parentPbf, parentZ, parentX, parentY, targetZ, targetX, targetY, options)
|
|
4
|
+
// Returns { data: Uint8Array, stats }
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
VectorTile,
|
|
8
|
+
VectorTileFeature,
|
|
9
|
+
VectorTileLayer,
|
|
10
|
+
} from "@mapbox/vector-tile";
|
|
11
|
+
import Protobuf from "pbf";
|
|
12
|
+
import Point from "@mapbox/point-geometry";
|
|
13
|
+
import { fromVectorTileJs } from "@maplibre/vt-pbf";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Subdivide parent tile PBF into higher zoom target tile.
|
|
17
|
+
* @param {ArrayBuffer|Uint8Array} parentPbf - Raw parent tile PBF bytes.
|
|
18
|
+
* @param {number} parentZ
|
|
19
|
+
* @param {number} parentX
|
|
20
|
+
* @param {number} parentY
|
|
21
|
+
* @param {number} targetZ
|
|
22
|
+
* @param {number} targetX
|
|
23
|
+
* @param {number} targetY
|
|
24
|
+
* @param {object} options
|
|
25
|
+
* @param {number} [options.buffer=128] - Clip buffer.
|
|
26
|
+
* @param {string[]|RegExp} [options.includeLayers] - Layer filter.
|
|
27
|
+
* @param {number} [options.maxDzWarn=8] - Warn if dz larger.
|
|
28
|
+
* @returns {{ data: Uint8Array, stats: any }}
|
|
29
|
+
*/
|
|
30
|
+
export function subdivideVectorTile(
|
|
31
|
+
parentPbf,
|
|
32
|
+
parentZ,
|
|
33
|
+
parentX,
|
|
34
|
+
parentY,
|
|
35
|
+
targetZ,
|
|
36
|
+
targetX,
|
|
37
|
+
targetY,
|
|
38
|
+
options = {},
|
|
39
|
+
) {
|
|
40
|
+
const t0 = performance.now ? performance.now() : Date.now();
|
|
41
|
+
const { buffer = 128, includeLayers, maxDzWarn = 8 } = options;
|
|
42
|
+
const dz = targetZ - parentZ;
|
|
43
|
+
if (dz <= 0) {
|
|
44
|
+
// Nothing to subdivide, caller should just use original bytes.
|
|
45
|
+
return { data: toUint8(parentPbf), stats: { dz, passthrough: true } };
|
|
46
|
+
}
|
|
47
|
+
if (dz > maxDzWarn && console && console.warn) {
|
|
48
|
+
console.warn(
|
|
49
|
+
`subdivideVectorTile: high dz=${dz}, performance may degrade.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
// Validate containment.
|
|
53
|
+
const scale = Math.pow(2, dz);
|
|
54
|
+
const expectedParentX = Math.floor(targetX / scale);
|
|
55
|
+
const expectedParentY = Math.floor(targetY / scale);
|
|
56
|
+
if (expectedParentX !== parentX || expectedParentY !== parentY) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Target tile not contained within parent tile coordinates.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const vt = new VectorTile(new Protobuf(toUint8(parentPbf)));
|
|
63
|
+
|
|
64
|
+
const overzoomed = new OverzoomedVectorTile();
|
|
65
|
+
let featuresIn = 0;
|
|
66
|
+
let featuresOut = 0;
|
|
67
|
+
for (const layerName in vt.layers) {
|
|
68
|
+
if (!Object.prototype.hasOwnProperty.call(vt.layers, layerName)) continue;
|
|
69
|
+
if (includeLayers) {
|
|
70
|
+
if (Array.isArray(includeLayers) && !includeLayers.includes(layerName))
|
|
71
|
+
continue;
|
|
72
|
+
if (includeLayers instanceof RegExp && !includeLayers.test(layerName))
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const sourceLayer = vt.layers[layerName];
|
|
76
|
+
const extent = sourceLayer.extent;
|
|
77
|
+
const offsetX = (targetX - parentX * scale) * extent;
|
|
78
|
+
const offsetY = (targetY - parentY * scale) * extent;
|
|
79
|
+
|
|
80
|
+
const featureWrappers = [];
|
|
81
|
+
for (let i = 0; i < sourceLayer.length; i++) {
|
|
82
|
+
const feature = sourceLayer.feature(i);
|
|
83
|
+
featuresIn++;
|
|
84
|
+
let geometry = feature.loadGeometry(); // Array of rings (Point[])
|
|
85
|
+
// Transform
|
|
86
|
+
for (const ring of geometry) {
|
|
87
|
+
for (const pt of ring) {
|
|
88
|
+
pt.x = pt.x * scale - offsetX;
|
|
89
|
+
pt.y = pt.y * scale - offsetY;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Clip
|
|
93
|
+
geometry = clipGeometry(geometry, feature.type, -buffer, extent + buffer);
|
|
94
|
+
if (!geometry || geometry.length === 0) continue;
|
|
95
|
+
featureWrappers.push(
|
|
96
|
+
new OverzoomedFeatureWrapper(
|
|
97
|
+
feature.type,
|
|
98
|
+
geometry,
|
|
99
|
+
feature.properties,
|
|
100
|
+
feature.id,
|
|
101
|
+
extent,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
featuresOut++;
|
|
105
|
+
}
|
|
106
|
+
if (featureWrappers.length > 0) {
|
|
107
|
+
overzoomed.addLayer(
|
|
108
|
+
new OverzoomedTileLayer(featureWrappers, layerName, extent),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const pbf = fromVectorTileJs(overzoomed);
|
|
113
|
+
const data =
|
|
114
|
+
pbf.byteOffset === 0 && pbf.byteLength === pbf.buffer.byteLength
|
|
115
|
+
? pbf
|
|
116
|
+
: new Uint8Array(pbf);
|
|
117
|
+
const t1 = performance.now ? performance.now() : Date.now();
|
|
118
|
+
return { data, stats: { dz, featuresIn, featuresOut, ms: t1 - t0 } };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Internal wrappers (adapted from overzoomed-geojson-vector-tile.ts) ---
|
|
122
|
+
class OverzoomedFeatureWrapper extends VectorTileFeature {
|
|
123
|
+
constructor(type, geometry, properties, id, extent) {
|
|
124
|
+
super(new Protobuf(), 0, extent, [], []);
|
|
125
|
+
this.type = type; // 1=Point,2=LineString,3=Polygon
|
|
126
|
+
this.properties = properties ? properties : {};
|
|
127
|
+
this.extent = extent;
|
|
128
|
+
this.myGeometry = geometry;
|
|
129
|
+
this.id = id;
|
|
130
|
+
}
|
|
131
|
+
loadGeometry() {
|
|
132
|
+
return this.myGeometry.map((ring) => ring.map((p) => new Point(p.x, p.y)));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class OverzoomedTileLayer extends VectorTileLayer {
|
|
137
|
+
constructor(features, name, extent) {
|
|
138
|
+
super(new Protobuf());
|
|
139
|
+
this._myFeatures = features;
|
|
140
|
+
this.name = name;
|
|
141
|
+
this.length = features.length;
|
|
142
|
+
this.extent = extent;
|
|
143
|
+
this.version = 2;
|
|
144
|
+
}
|
|
145
|
+
feature(i) {
|
|
146
|
+
return this._myFeatures[i];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class OverzoomedVectorTile {
|
|
151
|
+
constructor() {
|
|
152
|
+
this.layers = {};
|
|
153
|
+
}
|
|
154
|
+
addLayer(layer) {
|
|
155
|
+
this.layers[layer.name] = layer;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- Clipping helpers ---
|
|
160
|
+
function clipGeometry(geometry, type, min, max) {
|
|
161
|
+
switch (type) {
|
|
162
|
+
case 1:
|
|
163
|
+
return clipPoints(geometry, min, max); // POINT
|
|
164
|
+
case 2:
|
|
165
|
+
return clipLinesWrapper(geometry, min, max, false); // LINESTRING
|
|
166
|
+
case 3:
|
|
167
|
+
return clipLinesWrapper(geometry, min, max, true); // POLYGON
|
|
168
|
+
default:
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function clipPoints(geometry, min, max) {
|
|
173
|
+
const out = [];
|
|
174
|
+
for (const ring of geometry) {
|
|
175
|
+
for (const p of ring) {
|
|
176
|
+
if (p.x >= min && p.x <= max && p.y >= min && p.y <= max) {
|
|
177
|
+
out.push([p]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
function clipLinesWrapper(geometry, min, max, isPolygon) {
|
|
184
|
+
const out = [];
|
|
185
|
+
for (const line of geometry) {
|
|
186
|
+
const clipped = clipAxis(line, min, max, 0, isPolygon);
|
|
187
|
+
for (const seg of clipped) {
|
|
188
|
+
const clippedY = clipAxis(seg, min, max, 1, isPolygon);
|
|
189
|
+
for (const finalSeg of clippedY) out.push(finalSeg);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
function clipAxis(line, start, end, axis, isPolygon) {
|
|
195
|
+
const intersect = axis === 0 ? intersectX : intersectY;
|
|
196
|
+
let slice = [];
|
|
197
|
+
const out = [];
|
|
198
|
+
for (let i = 0; i < line.length - 1; i++) {
|
|
199
|
+
const p1 = line[i];
|
|
200
|
+
const p2 = line[i + 1];
|
|
201
|
+
const a = axis === 0 ? p1.x : p1.y;
|
|
202
|
+
const b = axis === 0 ? p2.x : p2.y;
|
|
203
|
+
let exited = false;
|
|
204
|
+
if (a < start && b > start) slice.push(intersect(p1, p2, start));
|
|
205
|
+
else if (a > end && b < end) slice.push(intersect(p1, p2, end));
|
|
206
|
+
else if (a >= start && a <= end) slice.push(p1);
|
|
207
|
+
if (b < start && a >= start) {
|
|
208
|
+
slice.push(intersect(p1, p2, start));
|
|
209
|
+
exited = true;
|
|
210
|
+
}
|
|
211
|
+
if (b > end && a <= end) {
|
|
212
|
+
slice.push(intersect(p1, p2, end));
|
|
213
|
+
exited = true;
|
|
214
|
+
}
|
|
215
|
+
if (!isPolygon && exited) {
|
|
216
|
+
out.push(slice);
|
|
217
|
+
slice = [];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const last = line[line.length - 1];
|
|
221
|
+
const lastVal = axis === 0 ? last.x : last.y;
|
|
222
|
+
if (lastVal >= start && lastVal <= end) slice.push(last);
|
|
223
|
+
if (
|
|
224
|
+
isPolygon &&
|
|
225
|
+
slice.length > 0 &&
|
|
226
|
+
!slice[0].equals(slice[slice.length - 1])
|
|
227
|
+
) {
|
|
228
|
+
slice.push(new Point(slice[0].x, slice[0].y));
|
|
229
|
+
}
|
|
230
|
+
if (slice.length > 0) out.push(slice);
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
function intersectX(p1, p2, x) {
|
|
234
|
+
const t = (x - p1.x) / (p2.x - p1.x);
|
|
235
|
+
return new Point(x, p1.y + (p2.y - p1.y) * t);
|
|
236
|
+
}
|
|
237
|
+
function intersectY(p1, p2, y) {
|
|
238
|
+
const t = (y - p1.y) / (p2.y - p1.y);
|
|
239
|
+
return new Point(p1.x + (p2.x - p1.x) * t, y);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function toUint8(data) {
|
|
243
|
+
if (data instanceof Uint8Array) return data;
|
|
244
|
+
return new Uint8Array(data);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export default subdivideVectorTile;
|