PageSourceSearch

https://energyexperts.ai/wp-content/plugins/energyExperts/assets/js/create-mapbox-trees.js?ver=2.25.616

js energyexperts.ai collected 2026-10-05 12:12:08 UTC 25,880 bytes, 693 lines download raw bytes

1/**
2 * BEREINIGTER MAPBOX TREE GENERATOR
3 * Vereinfacht ohne Hide/Show Logik
4 */
5
6class MapboxTreeGenerator {
7    constructor(mapInstance) {
8        this.map = mapInstance;
9        this.treeData = [];
10        this.isInitialized = false;
11    }
12
13    async createTreesInPolygon(polygon, season = 'summer', options = {}) {
14        try {
15            const geoJsonPolygon = this.polygonToGeoJSON(polygon);
16            if (!geoJsonPolygon) {
17                console.error(' Could not convert polygon');
18                return false;
19            }
20
21            const config = {
22                treeDensity: this.getTreeDensityForPolygon(polygon),
23                minDistance: Math.max(3, parseFloat(polygon.height) * 0.4),
24                treeTypes: ['oak', 'pine', 'birch'],
25                polygonHeight: parseFloat(polygon.height) || 0,
26                randomSeed: polygon.id,
27                ...options
28            };
29
30            this.removeTreesForPolygon(polygon.id);
31
32            // SCHRITT 2: Neue Trees für DIESES Polygon erstellen
33            const trees = await this.generateTreesInPolygon(geoJsonPolygon, config);
34            await this.createRealisticTrees(trees, season, polygon);
35
36            this.updateTreeLayer(options.callSetupMap !== false);
37
38            return true;
39        } catch (error) {
40            console.error(' Error creating trees:', error);
41            return false;
42        }
43    }
44
45
46    // FIXED: Gleiche ID-Logik wie Buildings
47    removeTreesForPolygon(polygonId) {
48        this.treeData = this.treeData.filter(treeFeature => {
49            const treeId = treeFeature.properties?.treeId || '';
50            const isFromThisPolygon = treeId.includes(`tree_${polygonId}_`);
51            return !isFromThisPolygon;  // ← Gleiche Logik wie Buildings
52        });
53    }
54
55    polygonToGeoJSON(polygon) {
56        try {
57            let coordinates = polygon.coordinates;
58
59            if (typeof coordinates === 'string') {
60                coordinates = JSON.parse(coordinates);
61            }
62
63            if (typeof coordinates[0][0] === 'number') {
64                coordinates = [coordinates];
65            }
66
67            // NEW: Vollständige Properties für Building-Features
68            return {
69                type: 'Feature',
70                geometry: {
71                    type: 'Polygon',
72                    coordinates: coordinates
73                },
74                properties: {
75                    // Original Properties (wie vorher)
76                    polygonId: polygon.id,
77                    id: polygon.id,                          // Additionally as 'id'
78
79                    // NEW: All popup properties hinzufügen
80                    name: polygon.name || `Gebäude ${polygon.id}`,
81                    height: parseFloat(polygon.height) || 3,  // Als Zahl, mit Fallback
82                    size: parseFloat(polygon.size) || this.calculatePolygonSize(polygon),
83                    color: polygon.color || '#8B4513',
84                    type: polygon.type || 'buildings',
85
86                    // NEW: Zusätzliche nützliche Properties
87                    created: polygon.created,
88                    width: polygon.width,                     // Falls rechteckiges Polygon
89                    length: polygon.length                    // Falls rechteckiges Polygon
90                }
91            };
92        } catch (error) {
93            console.error(' Error in polygon conversion:', error);
94            return null;
95        }
96    }
97
98    generateTreesInPolygon(geoJsonPolygon, config) {
99        try {
100            const area = turf.area(geoJsonPolygon);
101            const numTrees = Math.max(2, Math.floor(area / config.treeDensity));
102            const bbox = turf.bbox(geoJsonPolygon);
103            const candidatePoints = turf.randomPoint(numTrees * 2, { bbox: bbox });
104
105            const treesInside = candidatePoints.features.filter(point =>
106                turf.booleanPointInPolygon(point, geoJsonPolygon)
107            ).slice(0, numTrees);
108
109            const filteredTrees = this.filterByDistance(treesInside, config.minDistance);
110
111            // EXAKT WIE BUILDINGS: ID-Extraktion
112            const rawPolygonId = geoJsonPolygon.properties.polygonId ||
113                geoJsonPolygon.properties.id;
114
115            // EXAKT WIE BUILDINGS: Original-Polygon für ECHTE ID finden
116            const originalPolygon = projectData?.variations?.[variation]?.polygons?.find(p =>
117                p.id === rawPolygonId || p.id == rawPolygonId
118            );
119
120            // ECHTE DB-ID verwenden (falls Original-Polygon gefunden)
121            const polygonId = originalPolygon ? originalPolygon.id : rawPolygonId;
122
123            return filteredTrees.map((tree, index) => {
124                const treeId = `tree_${polygonId}_${index}`;
125
126                return {
127                    ...tree,
128                    properties: {
129                        treeId: treeId,
130                        type: config.treeTypes[index % config.treeTypes.length],
131                        height: this.getRandomTreeHeight(),
132                        radius: this.getRandomTreeRadius(),
133                        polygonId: polygonId,  // Echte DB-ID
134                        polygonHeight: config.polygonHeight || 0
135                    }
136                };
137            });
138
139        } catch (error) {
140            console.error(' Error in tree generation:', error);
141            return [];
142        }
143    }
144
145    filterByDistance(trees, minDistanceMeters) {
146        const filtered = [];
147        const minDistanceKm = minDistanceMeters / 1000;
148
149        trees.forEach(tree => {
150            const tooClose = filtered.some(existing =>
151                turf.distance(tree, existing, { units: 'kilometers' }) < minDistanceKm
152            );
153
154            if (!tooClose) {
155                filtered.push(tree);
156            }
157        });
158
159        return filtered;
160    }
161
162    async createRealisticTrees(trees, season, polygon) {
163        const colors = this.getSeasonColors(season);
164
165        // SEQUENTIELL mit Pausen — verhindert Main Thread Blocking
166        // Batches von 3 Baeumen, dann Main Thread atmen lassen
167        const batchSize = 5;
168        for (let i = 0; i < trees.length; i += batchSize) {
169            const batch = trees.slice(i, i + batchSize);
170            // Innerhalb des Batches: sequentiell statt Promise.all
171            for (let j = 0; j < batch.length; j++) {
172                await this.generateSingleTree(batch[j], colors, polygon);
173            }
174
175            // Kurze Pause zwischen Batches — Main Thread frei geben
176            if (i + batchSize < trees.length) {
177                await new Promise(resolve => setTimeout(resolve, 20));
178            }
179        }
180    }
181
182    async generateSingleTree(tree, colors, polygon) {
183        try {
184            const coords = tree.geometry.coordinates;
185            const treeProps = tree.properties;
186
187            // EXAKT WIE BUILDINGS: Original-Polygon finden für echte DB-ID
188            const originalPolygon = projectData?.variations?.[variation]?.polygons?.find(p =>
189                p.id === polygon.id || p.id == polygon.id
190            );
191
192            // Echte DB-ID verwenden
193            const realPolygonId = originalPolygon ? originalPolygon.id : polygon.id;
194
195            // Polygon-Daten (ECHTE ID verwenden)
196            const polygonData = {
197                polygonId: realPolygonId,  // ECHTE DB-ID!
198                name: (originalPolygon || polygon).name || 'Wald',
199                size: parseFloat((originalPolygon || polygon).size) || 0.1,
200                polygonColor: (originalPolygon || polygon).color || '#228B22',
201                type: (originalPolygon || polygon).type === 'obstacle' ? 'trees' : (originalPolygon || polygon).type || 'trees'
202            };
203
204            // Rest der Tree-Generierung bleibt gleich...
205            let treeHeight = treeProps.height || 5;
206            const polygonHeight = Math.max(0.5, treeProps.polygonHeight || parseFloat((originalPolygon || polygon).height) || 0.5);
207
208            if (polygonHeight >= 0.5) {
209                const heightFactor = 0.7 + Math.random() * 0.5;
210                treeHeight = Math.max(0.5, polygonHeight * heightFactor + Math.random() * 2);
211            }
212
213            const trunkHeight = treeHeight * 0.3;
214            const trunkRadius = 0.2 + Math.random() * 0.3;
215
216            // TRUNK mit echter DB-ID
217            const trunk = turf.circle(coords, trunkRadius, {
218                steps: 8,
219                units: 'meters',
220                properties: {
221                    polygonId: polygonData.polygonId,  // ECHTE DB-ID
222                    name: polygonData.name,
223                    height: trunkHeight,
224                    size: polygonData.size,
225                    color: '#8B4513',
226                    type: polygonData.type,
227                    base_height: 0,
228                    treeId: treeProps.treeId,
229                    part: 'trunk'
230                }
231            });
232
233            this.treeData.push(trunk);
234
235            const crownHeight = treeHeight - trunkHeight;
236            const numLayers = Math.max(8, Math.floor(treeHeight * 1.5));
237            const maxRadius = Math.max(1, treeHeight * 0.3);
238
239            // Crown mit echter DB-ID
240            await this.generateTreeCrown(coords, numLayers, trunkHeight, crownHeight, maxRadius, colors, treeProps, polygonData);
241
242        } catch (error) {
243            console.error(' Error in tree generation:', error);
244        }
245    }
246
247// ============================================================================
248// ERWEITERTE generateTreeCrown - FEATURE-HÖHE + POLYGON-DATEN
249// ============================================================================
250
251    async generateTreeCrown(center, numLayers, baseHeight, crownHeight, maxRadius, colors, treeProps, polygonData) {
252        try {
253            const layerHeight = crownHeight / numLayers;
254
255            for (let i = 0; i <= numLayers; i++) {
256                const currentHeight = baseHeight + (i * layerHeight);
257                const progress = i / numLayers;
258
259                let radius;
260                if (progress <= 0.6) {
261                    radius = maxRadius * (progress / 0.6);
262                } else {
263                    radius = maxRadius * (1 - (progress - 0.6) / 0.4);
264                }
265
266                radius = Math.max(0.2, radius + (Math.random() - 0.5) * 0.5);
267
268                if (radius > 0.2) {
269                    const crownColor = this.getColorBetween(colors.light, colors.dark, progress);
270                    const crownColorStr = `rgb(${crownColor[0]}, ${crownColor[1]}, ${crownColor[2]})`;
271                    const crownLayer = this.createOrganicCrownLayer(center, radius, treeProps.treeId);
272
273                    if (crownLayer) {
274                        crownLayer.properties = {
275                            // POPUP-PROPERTIES:
276                            polygonId: polygonData.polygonId,
277                            name: polygonData.name,              // ← VOM POLYGON (gleich für alle Features)
278                            height: currentHeight + layerHeight, // ← FEATURE-SPEZIFISCH (Crown-Layer-Höhe)
279                            size: polygonData.size,              // ← VOM POLYGON (gleich für alle Features)
280                            color: polygonData.polygonColor || crownColorStr,                // ← FEATURE-SPEZIFISCH (Crown-Farbe)
281                            type: polygonData.type,
282
283                            // Rendering-Properties
284                            base_height: currentHeight,
285
286                            // Tree-spezifische Properties
287                            treeId: treeProps.treeId,
288                            part: 'crown',
289                            layer: i
290                        };
291
292                        this.treeData.push(crownLayer);
293                    }
294                }
295            }
296        } catch (error) {
297            console.error(' Error in crown generation:', error);
298        }
299    }
300
301
302    createOrganicCrownLayer(center, radius, treeId) {
303        try {
304            // OPTIMIERT: Weniger Kreise für bessere Performance
305            const numCircles = Math.max(2, Math.floor(radius * 2));
306            const circles = [];
307
308            for (let j = 0; j < numCircles; j++) {
309                const angle = (j / numCircles) * 2 * Math.PI;
310                const distance = radius * 0.4 * Math.random();
311                const circleCenter = [
312                    center[0] + Math.cos(angle) * distance / 111320,
313                    center[1] + Math.sin(angle) * distance / 111320
314                ];
315
316                const circleRadius = radius * (0.3 + Math.random() * 0.4);
317                const circle = turf.circle(circleCenter, circleRadius, {
318                    steps: 8, // OPTIMIERT: Weniger Steps
319                    units: 'meters'
320                });
321                circles.push(circle);
322            }
323
324            const mainCircle = turf.circle(center, radius * 0.7, {
325                steps: 12,
326                units: 'meters'
327            });
328            circles.push(mainCircle);
329
330            const collection = turf.featureCollection(circles);
331            const dissolved = turf.dissolve(collection);
332
333            return dissolved.features.length > 0 ? dissolved.features[0] : mainCircle;
334
335        } catch (error) {
336            return turf.circle(center, radius, { steps: 8, units: 'meters' });
337        }
338    }
339
340    async updateTreeLayer(callSetupMap = true) {
341        // Alle Trees loeschen und neu erstellen
342        this.treeData = [];
343
344        // Warte auf Style bevor Layer erstellt werden
345        if (this.map && !this.map.isStyleLoaded()) {
346            await new Promise((resolve) => {
347                this.map.on('style.load', resolve);
348                // Safety timeout nach 10s
349                setTimeout(resolve, 10000);
350            });
351        }
352
353        // Alle Tree-Polygone finden — mit Guard fuer polygonManager
354        const allPolygons = projectData?.variations?.[variation]?.polygons || [];
355
356        var pm = (typeof polygonManager !== 'undefined' && polygonManager)
357            ? polygonManager
358            : (window.polygonManager || null);
359
360        const treePolygons = allPolygons.filter(polygon => {
361            if (pm && typeof pm.isTreePolygon === 'function') {
362                return pm.isTreePolygon(polygon);
363            }
364            // Fallback: eigene Detection
365            if (polygon.type === 'obstacle' || polygon.type === 'trees') return true;
366            if (!polygon.name) return false;
367            var name = polygon.name.toLowerCase();
368            return ['wald', 'baum', 'tree', 'forest', 'gehoelz', 'hecke'].some(function(kw) { return name.indexOf(kw) !== -1; });
369        });
370
371        // Trees fuer alle Tree-Polygone erstellen
372        for (const polygon of treePolygons) {
373            const config = {
374                treeDensity: this.getTreeDensityForPolygon(polygon),
375                minDistance: Math.max(3, parseFloat(polygon.height) * 0.4),
376                treeTypes: ['oak', 'pine', 'birch'],
377                polygonHeight: parseFloat(polygon.height) || 0,
378                randomSeed: polygon.id
379            };
380
381            const trees = this.generateTreesInPolygon(this.polygonToGeoJSON(polygon), config);
382            await this.createRealisticTrees(trees, 'summer', polygon);
383        }
384
385        // Layer aktualisieren mit Retry
386        const collection = turf.featureCollection(this.treeData);
387
388        var maxRetries = 3;
389        for (var attempt = 1; attempt <= maxRetries; attempt++) {
390            try {
391                const treesLayer = layers.find(l => l.id === 'trees-3d');
392                if (treesLayer) {
393                    treesLayer.features = collection;
394                } else {
395                    layers.push({
396                        id: 'trees-3d',
397                        type: '3d-tree',
398                        fillId: 'trees-3d-layer',
399                        features: collection
400                    });
401                }
402
403                if (callSetupMap) {
404                    setupMap(0, 'trees-3d');
405                }
406
407                // Erfolgreich — Schleife verlassen
408                break;
409
410            } catch (error) {
411                console.warn('[Trees] Layer update error attempt ' + attempt + '/' + maxRetries + ': ' + error.message);
412                if (attempt < maxRetries) {
413                    await new Promise(resolve => setTimeout(resolve, 500));
414                } else {
415                    console.error('[Trees] Layer update after ' + maxRetries + ' attempts failed:', error);
416                }
417            }
418        }
419    }
420
421    async rebuildAllTrees(callSetupMap = true) {
422
423        // Alle Trees löschen
424        this.treeData = [];
425
426        // Alle Tree-Polygone finden
427        const allPolygons = projectData?.variations?.[variation]?.polygons || [];
428        const treePolygons = allPolygons.filter(polygon =>
429            this.isTreePolygon(polygon)  // ← Eigene Methode verwenden
430        );
431
432        // Trees für alle Tree-Polygone erstellen
433        for (const polygon of treePolygons) {
434            const config = {
435                treeDensity: this.getTreeDensityForPolygon(polygon),
436                minDistance: Math.max(3, parseFloat(polygon.height) * 0.4),
437                treeTypes: ['oak', 'pine', 'birch'],
438                polygonHeight: parseFloat(polygon.height) || 0,
439                randomSeed: polygon.id
440            };
441
442            const geoJsonPolygon = this.polygonToGeoJSON(polygon);
443            if (geoJsonPolygon) {
444                const trees = this.generateTreesInPolygon(geoJsonPolygon, config);
445                await this.createRealisticTrees(trees, 'summer', polygon);
446            }
447        }
448
449        // Layer aktualisieren
450        this.updateTreeLayer(callSetupMap);
451    }
452
453    // NEW: getTreeDensityForPolygon ins TreeGenerator
454    getTreeDensityForPolygon(polygon) {
455        const area = parseFloat(polygon.size) * 10000; // ha zu m²
456        const height = parseFloat(polygon.height) || 0;
457
458        // Baumdichte basierend auf Höhe anpassen
459        let baseDensity = 40;
460        if (height > 15) baseDensity = 50;
461        else if (height > 8) baseDensity = 35;
462        else baseDensity = 30;
463
464        // Flächenbasierte Anpassung
465        if (area < 1000) return baseDensity * 0.8;
466        if (area < 5000) return baseDensity;
467        return baseDensity * 1.2;
468    }
469
470    clearTrees() {
471        this.treeData = [];
472        if (this.map.getSource('trees-3d')) {
473            this.map.getSource('trees-3d').setData(turf.featureCollection([]));
474        }
475    }
476
477    reloadTreeLayer() {
478        try {
479            if (this.map.getLayer('trees-3d-layer')) {
480                this.map.removeLayer('trees-3d-layer');
481            }
482            if (this.map.getSource('trees-3d')) {
483                this.map.removeSource('trees-3d');
484            }
485
486            this.updateTreeLayer(true);
487        } catch (error) {
488            console.error(' Error in layer reload:', error);
489        }
490    }
491
492    getSeasonColors(season) {
493        const seasonColors = {
494            summer: { light: [116, 196, 118], dark: [0, 68, 27] },
495            autumn: { light: [254, 224, 139], dark: [215, 48, 39] },
496            winter: { light: [255, 255, 255], dark: [0, 68, 27] },
497            spring: { light: [144, 238, 144], dark: [34, 139, 34] }
498        };
499        return seasonColors[season] || seasonColors.summer;
500    }
501
502    getColorBetween(color1, color2, weight) {
503        const w1 = weight;
504        const w2 = 1 - w1;
505        return [
506            Math.round(color1[0] * w1 + color2[0] * w2),
507            Math.round(color1[1] * w1 + color2[1] * w2),
508            Math.round(color1[2] * w1 + color2[2] * w2)
509        ];
510    }
511
512    getRandomTreeHeight() {
513        return 6 + Math.random() * 12;
514    }
515
516    getRandomTreeRadius() {
517        return 2 + Math.random() * 3;
518    }
519}
520
521// ============================================================================
522// VEREINFACHTE GLOBAL FUNCTIONS
523// ============================================================================
524
525let treeGenerator = null;
526
527async function initializeTreeGenerator(mapInstance) {
528    treeGenerator = new MapboxTreeGenerator(mapInstance);
529
530    // Warte auf Style-Load bevor Baeume geladen werden
531    if (mapInstance && !mapInstance.isStyleLoaded()) {
532        await new Promise(function(resolve) {
533            mapInstance.on('style.load', resolve);
534            setTimeout(resolve, 10000);
535        });
536    }
537
538    // Bestehende Baeume werden vom PolygonManager geladen (sequentiell mit Pausen).
539    // NICHT hier laden — sonst Double-Loading und Main Thread Freeze.
540    // loadExistingTreePolygons(); // Deaktiviert: PolygonManager uebernimmt
541}
542
543async function updateAllTrees() {
544    if (treeGenerator) {
545        await treeGenerator.updateTreeLayer(false);
546        setupMap(0, 'trees-3d');
547    }
548}
549
550async function loadExistingTreePolygons() {
551    try {
552        if (typeof projectData === 'undefined' || typeof variation === 'undefined') {
553            console.warn('[Trees] projectData or variation not available — skipping batch load');
554            return;
555        }
556
557        // Warte bis treeGenerator bereit ist (max 10s)
558        var maxWait = 20;
559        var waited = 0;
560        while (waited < maxWait && !treeGenerator) {
561            await new Promise(resolve => setTimeout(resolve, 500));
562            waited++;
563        }
564
565        if (!treeGenerator) {
566            console.error('[Trees] treeGenerator not available after 10s — skipping');
567            return;
568        }
569
570        // Warte auf map style loaded
571        if (treeGenerator.map && !treeGenerator.map.isStyleLoaded()) {
572            await new Promise(function(resolve) {
573                treeGenerator.map.on('style.load', resolve);
574                setTimeout(resolve, 10000);
575            });
576        }
577
578        const allPolygons = projectData?.variations?.[variation]?.polygons || [];
579
580        // polygonManager mit Fallback
581        var pm = (typeof polygonManager !== 'undefined' && polygonManager)
582            ? polygonManager
583            : (window.polygonManager || null);
584
585        const treePolygons = allPolygons.filter(function(polygon) {
586            if (pm && typeof pm.isTreePolygon === 'function') {
587                return pm.isTreePolygon(polygon);
588            }
589            // Fallback-Detection ohne polygonManager
590            if (polygon.type === 'obstacle' || polygon.type === 'trees') return true;
591            if (!polygon.name) return false;
592            var name = polygon.name.toLowerCase();
593            return ['wald', 'baum', 'tree', 'forest', 'gehoelz', 'hecke'].some(function(kw) { return name.indexOf(kw) !== -1; });
594        });
595
596        if (treePolygons.length > 0) {
597            for (var i = 0; i < treePolygons.length; i++) {
598                var polygon = treePolygons[i];
599                try {
600                    await createTreesInPolygonSilent(polygon, 'summer', {
601                        polygonHeight: parseFloat(polygon.height) || 0.5,
602                        randomSeed: polygon.id
603                    });
604                } catch (err) {
605                    console.warn('[Trees] Error with polygon ' + polygon.id + ', skipping: ' + err.message);
606                }
607                await new Promise(resolve => setTimeout(resolve, 200));
608            }
609
610            // setupMap nur EINMAL am Ende — mit Retry
611            var setupRetries = 3;
612            for (var r = 1; r <= setupRetries; r++) {
613                try {
614                    setupMap(0, 'trees-3d');
615                    break;
616                } catch (err) {
617                    console.warn('[Trees] setupMap error attempt ' + r + '/' + setupRetries + ': ' + err.message);
618                    if (r < setupRetries) {
619                        await new Promise(resolve => setTimeout(resolve, 500));
620                    }
621                }
622            }
623        }
624
625    } catch (error) {
626        console.error('[Trees] Error loading existing tree polygons:', error);
627    }
628}
629
630// New silent version for batch loading
631async function createTreesInPolygonSilent(polygon, season = 'summer', options = {}) {
632    if (!treeGenerator) {
633        console.error(' Tree generator not initialized!');
634        return false;
635    }
636
637    const polygonHeight = Math.max(0.5, parseFloat(polygon.height) || 0.5);
638    const extendedOptions = {
639        polygonHeight: polygonHeight,
640        randomSeed: polygon.id,
641        callSetupMap: false, // No setupMap bei Batch-Loading
642        ...options
643    };
644
645    return await treeGenerator.createTreesInPolygon(polygon, season, extendedOptions);
646}
647
648
649async function createTreesInPolygon(polygon, season = 'summer', options = {}) {
650    if (!treeGenerator) {
651        console.error(' Tree generator not initialized!');
652        return false;
653    }
654
655    const polygonHeight = Math.max(0.5, parseFloat(polygon.height) || 0.5);
656
657    // FIXED: Konsistente Options mit randomSeed
658    const extendedOptions = {
659        polygonHeight: polygonHeight,
660        randomSeed: polygon.id, // WICHTIG: Immer die Polygon-ID als Seed verwenden
661        ...options
662    };
663
664    return await treeGenerator.createTreesInPolygon(polygon, season, extendedOptions);
665}
666
667// VEREINFACHT: Nur noch diese Funktionen
668function clearAllTrees() {
669    if (treeGenerator) {
670        treeGenerator.clearTrees();
671    }
672}
673
674function reloadTreeLayer() {
675    if (treeGenerator) {
676        treeGenerator.reloadTreeLayer();
677    }
678}
679
680// NEW: Nur Bäume für ein bestimmtes Polygon löschen
681function clearTreesForPolygon(polygonId) {
682    if (treeGenerator) {
683        treeGenerator.removeTreesForPolygon(polygonId);
684
685        // NUR Layer-Update
686        const collection = turf.featureCollection(treeGenerator.treeData);
687        const treesLayer = layers.find(l => l.id === 'trees-3d');
688        if (treesLayer) {
689            treesLayer.features = collection;
690        }
691        setupMap(0, 'trees-3d');
692    }
693}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.