MCPcopy Create free account
hub / github.com/facebook/Rapid / _mergeLineStrings

Method _mergeLineStrings

modules/services/VectorTileService.js:656–795  ·  view source on GitHub ↗

* _mergeLineStrings * Merge LineString features that share a prophash and have near-coincident * endpoints along a tile edge. This is the LineString equivalent of * `_mergePolygons` — it stitches road segments split by MVT tile clipping. * * @param {Object} cache * @param {stri

(cache, prophash, featureIDs, lowTile, highTile)

Source from the content-addressed store, hash-verified

654 * @param {Tile} highTile
655 */
656 _mergeLineStrings(cache, prophash, featureIDs, lowTile, highTile) {
657 const features = Array.from(featureIDs)
658 .map(id => cache.features.get(id))
659 .filter(f => f && f.geojson?.geometry?.type === 'LineString');
660
661 if (features.length < 2) return;
662
663 const SNAP_TOL = 5e-5; // ~5.5 m at equator
664
665 // Build a working list of coordinate arrays (+ a reference to source properties)
666 let lines = features.map(f => ({
667 coords: f.geojson.geometry.coordinates.slice(), // shallow copy of array
668 properties: f.geojson.properties
669 }));
670
671 // Iteratively merge pairs of lines whose endpoints are close together
672 // AND whose approach directions are compatible (prevents merging perpendicular roads)
673 const MAX_MERGE_ANGLE = 30; // degrees — roads must approach within 30° of each other
674 let didMerge = true;
675 while (didMerge) {
676 didMerge = false;
677 for (let i = 0; i < lines.length && !didMerge; i++) {
678 for (let j = i + 1; j < lines.length && !didMerge; j++) {
679 const a = lines[i].coords;
680 const b = lines[j].coords;
681 const aFirst = a[0];
682 const aLast = a[a.length - 1];
683 const bFirst = b[0];
684 const bLast = b[b.length - 1];
685
686 let merged = null;
687
688 if (this._pointsClose(aLast, bFirst, SNAP_TOL) && this._directionsCompatible(a, b, MAX_MERGE_ANGLE)) {
689 merged = this._joinLineCoords(a, b, SNAP_TOL); // A ──→ B
690 } else if (this._pointsClose(aFirst, bLast, SNAP_TOL) && this._directionsCompatible(b, a, MAX_MERGE_ANGLE)) {
691 merged = this._joinLineCoords(b, a, SNAP_TOL); // B ──→ A
692 } else if (this._pointsClose(aLast, bLast, SNAP_TOL)) {
693 const bRev = b.slice().reverse();
694 if (this._directionsCompatible(a, bRev, MAX_MERGE_ANGLE)) {
695 merged = this._joinLineCoords(a, bRev, SNAP_TOL); // A ──→ rev(B)
696 }
697 } else if (this._pointsClose(aFirst, bFirst, SNAP_TOL)) {
698 const aRev = a.slice().reverse();
699 if (this._directionsCompatible(aRev, b, MAX_MERGE_ANGLE)) {
700 merged = this._joinLineCoords(aRev, b, SNAP_TOL); // rev(A) ──→ B
701 }
702 }
703
704 if (merged) {
705 lines[i] = { coords: merged, properties: lines[i].properties };
706 lines.splice(j, 1);
707 didMerge = true;
708 }
709 }
710 }
711 }
712
713 // ── Re-cache results ───────────────────────────────────────────────────

Callers 1

_processMergeQueueMethod · 0.95

Calls 12

_pointsCloseMethod · 0.95
_directionsCompatibleMethod · 0.95
_joinLineCoordsMethod · 0.95
_uncacheFeatureIDsMethod · 0.95
_calcExtentMethod · 0.95
getNextIDMethod · 0.95
_queueMergeMethod · 0.95
_cacheFeaturesMethod · 0.95
filterMethod · 0.80
getMethod · 0.80
reverseMethod · 0.80
deferredRedrawMethod · 0.45

Tested by

no test coverage detected