1+ const markers = [ ] ;
2+
3+ function InitializeMapAndData ( ) {
4+
5+
6+ // Initialize Map
7+
8+ var map = L . map ( 'map' , { zoomControl : false } ) ;
9+
10+
11+ /*
12+ L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
13+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OSM</a> & CartoDB',
14+ subdomains: 'abcd',
15+ maxZoom: 19,
16+ }).addTo(map);
17+
18+
19+ L.tileLayer('https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png', {
20+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/">CARTO</a>',
21+ subdomains: 'abcd',
22+ maxZoom: 20,
23+ }).addTo(map);
24+ */ mapbox://styles/kobers/cm9e2m79p00fx01r3ahud1s52mapbox://styles/kobers/cm9e2m79p00fx01r3ahud1s52
25+
26+
27+ L . tileLayer ( 'https://api.mapbox.com/styles/v1/{id}/tiles/512/{z}/{x}/{y}@2x?access_token={accessToken}' , {
28+ id : 'kobers/cm9e2m79p00fx01r3ahud1s52' ,
29+ accessToken : 'pk.eyJ1Ijoia29iZXJzIiwiYSI6ImNsNjUwNTIzZzAxbmMzYm5vZGsxaTF2OTEifQ.dsJnTEm6J2YYWTN1W7ZvYA' ,
30+ tileSize : 512 ,
31+ zoomOffset : - 1 ,
32+ attribution : '© <a href="https://www.mapbox.com/">Mapbox</a>'
33+ } ) . addTo ( map ) ;
34+
35+
36+ // Create bounds object
37+ var bounds = L . latLngBounds ( ) ;
38+
39+ // Fetch and process product batch steps
40+ fetch ( "https://smartfarminglab.github.io/data/productpassport.json" )
41+ . then ( response => {
42+ if ( ! response . ok ) {
43+ throw new Error ( `HTTP error! Status: ${ response . status } ` ) ;
44+ }
45+ return response . json ( ) ;
46+ } )
47+ . then ( data => {
48+
49+ // Display Product Card Info
50+ // document.getElementById("product-card").style.display = "grid";
51+ document . getElementById ( "product-name" ) . textContent = data . productName ;
52+ // document.getElementById("product-category").textContent = data.category;
53+ document . getElementById ( "product-category-tag" ) . textContent = data . category ;
54+ const options = { day : '2-digit' , month : '2-digit' , year : 'numeric' } ;
55+ document . getElementById ( "production-date" ) . textContent =
56+ new Date ( data . productionDate ) . toLocaleDateString ( "de-DE" , options ) ;
57+
58+ document . getElementById ( "best-before" ) . textContent = new Date ( data . bestBefore ) . toLocaleDateString ( ) ;
59+ document . getElementById ( "company-name" ) . textContent = data . productOwner . companyName ;
60+ document . getElementById ( "product-brand" ) . textContent = data . productOwner . companyName ;
61+ document . getElementById ( "product-description" ) . textContent = data . description ;
62+
63+ //document.getElementById("product-image").src = data.imageSrc;
64+ //document.getElementById("product-image").src = data.imageSrc !== "string" ? data.imageSrc : "https://via.placeholder.com/100";
65+
66+ if ( data . productOwner . imageSrc != "undefined" ) {
67+
68+ document . getElementById ( "company-logo" ) . src = data . productOwner . imageSrc ;
69+
70+
71+ }
72+
73+
74+
75+ const container = document . getElementById ( "product-image-container" ) ;
76+ container . style . backgroundImage =
77+ typeof data . imageSrc === "string"
78+ ? `url('${ data . imageSrc } ')`
79+ : "url('https://via.placeholder.com/100')" ;
80+
81+
82+ // Load Certificates
83+ let certificateContainer = document . getElementById ( "certificates" ) ;
84+ certificateContainer . innerHTML = "" ;
85+ data . certificates . forEach ( cert => {
86+ let img = document . createElement ( "img" ) ;
87+ img . src = cert . imageSrc ;
88+ img . title = cert . name ;
89+ img . onclick = ( ) => window . open ( cert . link , "_blank" ) ;
90+ certificateContainer . appendChild ( img ) ;
91+ } ) ;
92+
93+
94+ let ingredientsContainer = document . getElementById ( "ingredients" ) ;
95+ ingredientsContainer . innerHTML = "," ;
96+
97+ if ( ! data . ingredients || data . ingredients . length === 0 ) {
98+ console . warn ( "No ingredients found." ) ;
99+ } else {
100+ data . ingredients . forEach ( ( ingredient , index ) => {
101+ ingredientsContainer . innerHTML += ", " + ingredient . name + " " + ingredient . percentage + "%" ;
102+ } ) ;
103+
104+ ingredientsContainer . innerHTML = ingredientsContainer . innerHTML . replace ( ",, " , "" ) ;
105+ }
106+
107+
108+ if ( ! data . productBatchSteps || data . productBatchSteps . length === 0 ) {
109+ console . warn ( "No product batch steps found." ) ;
110+ map . setView ( [ 51.1657 , 10.4515 ] , 6 ) ; // Fallback center
111+ return ;
112+ }
113+ else {
114+
115+ const stepsList = document . getElementById ( "steps-list" ) ;
116+
117+ // Clear existing list items (optional)
118+ stepsList . innerHTML = "" ;
119+
120+ // Fill with new steps
121+ data . productBatchSteps . forEach ( ( step , index ) => {
122+ const li = document . createElement ( "li" ) ;
123+ li . textContent = `${ index + 1 } . ${ step . name } ` ;
124+ stepsList . appendChild ( li ) ;
125+ } ) ;
126+ }
127+
128+ // Clustergruppe initialisieren
129+ const markerClusterGroup = L . markerClusterGroup ( ) ;
130+
131+ let polylinePoints = [ ] ;
132+ data . productBatchSteps . forEach ( step => {
133+ if ( step . address && step . address . latitude && step . address . longitude ) {
134+ var marker = L . marker ( [ step . address . latitude , step . address . longitude ] , {
135+ icon : L . divIcon ( {
136+ className : 'custom-marker' ,
137+ html : `<div></div>` ,
138+ iconSize : [ 35 , 35 ] ,
139+ popupAnchor : [ 0 , - 10 ]
140+ } )
141+ } ) . on ( 'click' , function ( e ) {
142+
143+ markers . forEach ( m => {
144+ const el = m . getElement ( ) ;
145+ if ( el ) el . classList . remove ( 'pulse' ) ;
146+ } ) ;
147+
148+ // Step 3: Add 'pulse' to clicked marker
149+ const el = marker . getElement ( ) ;
150+ if ( el ) el . classList . add ( 'pulse' ) ;
151+
152+ showSidebarWithStep ( e , step ) ;
153+ } ) ;
154+
155+
156+ markers . push ( marker ) ;
157+
158+
159+
160+ //.addTo(map)
161+ //.bindPopup(`<b>${step.name}</b><br>${step.address.name}, ${step.address.city}`);
162+
163+
164+ // Zur Clustergruppe hinzufügen
165+ markerClusterGroup . addLayer ( marker ) ;
166+
167+ // Extend bounds
168+ bounds . extend ( [ step . address . latitude , step . address . longitude ] ) ;
169+ polylinePoints . push ( [ step . address . latitude , step . address . longitude ] ) ;
170+ }
171+ } ) ;
172+
173+
174+ // Cluster zur Karte hinzufügen
175+ map . addLayer ( markerClusterGroup ) ;
176+
177+
178+ const animateWithCSS = true ; // auf false setzen für Punkt-für-Punkt-Zeichnung
179+
180+ if ( polylinePoints . length > 1 ) {
181+ for ( let i = 0 ; i < polylinePoints . length - 1 ; i ++ ) {
182+ const start = polylinePoints [ i ] ;
183+ const end = polylinePoints [ i + 1 ] ;
184+
185+ // Midpoint mit Kurven-Offset berechnen
186+ const offsetX = end [ 1 ] - start [ 1 ] ;
187+ const offsetY = end [ 0 ] - start [ 0 ] ;
188+ const r = Math . sqrt ( offsetX * offsetX + offsetY * offsetY ) ;
189+ const theta = Math . atan2 ( offsetY , offsetX ) ;
190+ const thetaOffset = Math . PI / 10 ;
191+
192+ const r2 = ( r / 2 ) / Math . cos ( thetaOffset ) ;
193+ const theta2 = theta + thetaOffset ;
194+
195+ const midpointX = ( r2 * Math . cos ( theta2 ) ) + start [ 1 ] ;
196+ const midpointY = ( r2 * Math . sin ( theta2 ) ) + start [ 0 ] ;
197+ const midpointLatLng = [ midpointY , midpointX ] ;
198+
199+ const pathOptions = {
200+ color : '#3c8138' ,
201+ weight : 2 ,
202+ opacity : 0.8 ,
203+ } ;
204+
205+ if ( animateWithCSS ) {
206+ // CSS-animated
207+ const curvedPath = L . curve ( [
208+ 'M' , start ,
209+ 'Q' , midpointLatLng ,
210+ end
211+ ] , pathOptions ) . addTo ( map ) ;
212+
213+ setTimeout ( ( ) => {
214+ if ( curvedPath . _path ) {
215+ const path = curvedPath . _path ;
216+ const length = path . getTotalLength ( ) ;
217+
218+ path . style . strokeDasharray = length ;
219+ path . style . strokeDashoffset = length ;
220+ path . style . animation = `drawLine 2s ease forwards` ;
221+ }
222+ } , 0 ) ; // sicherstellen, dass _path verfügbar ist
223+
224+
225+ } else {
226+ // Punkt-für-Punkt Zeichnung mit interpolierter Polyline
227+ const numSteps = 100 ;
228+ const latlngs = [ ] ;
229+ for ( let t = 0 ; t <= 1 ; t += 1 / numSteps ) {
230+ // Quadratische Bézier-Interpolation
231+ const x = Math . pow ( 1 - t , 2 ) * start [ 1 ] +
232+ 2 * ( 1 - t ) * t * midpointLatLng [ 1 ] +
233+ Math . pow ( t , 2 ) * end [ 1 ] ;
234+ const y = Math . pow ( 1 - t , 2 ) * start [ 0 ] +
235+ 2 * ( 1 - t ) * t * midpointLatLng [ 0 ] +
236+ Math . pow ( t , 2 ) * end [ 0 ] ;
237+ latlngs . push ( [ y , x ] ) ;
238+ }
239+
240+ const animatedLine = L . polyline ( [ ] , pathOptions ) . addTo ( map ) ;
241+
242+ let i = 0 ;
243+ const interval = setInterval ( ( ) => {
244+ if ( i < latlngs . length ) {
245+ animatedLine . addLatLng ( latlngs [ i ] ) ;
246+
247+
248+ i ++ ;
249+ } else {
250+ clearInterval ( interval ) ;
251+ }
252+ } , 10 ) ; // Geschwindigkeit der Animation
253+ }
254+ }
255+ }
256+
257+
258+
259+
260+
261+
262+ // Fit bounds to show all points
263+ if ( bounds . isValid ( ) ) {
264+ //map.fitBounds(bounds);
265+ const zoom = map . getBoundsZoom ( bounds ) ;
266+ map . setView ( bounds . getCenter ( ) , zoom - 0.5 ) ; // Zoom um 1 verringert
267+
268+ } else {
269+ console . warn ( "No valid coordinates found." ) ;
270+ map . setView ( [ 51.1657 , 10.4515 ] , 6 ) ; // Default to Germany
271+ }
272+ } )
273+ . catch ( error => console . error ( "Error loading data:" , error ) ) ;
274+
275+ }
276+
277+
278+ function removePulse ( ) {
279+ markers . forEach ( m => {
280+ const el = m . getElement ( ) ;
281+ if ( el ) el . classList . remove ( 'pulse' ) ;
282+ } ) ;
283+ }
0 commit comments