1var k = null, kmini = null; 2 3var giga = { 4 5 advanced: false, 6 7 wikiCache: {}, 8 prevPanID: '', 9 10 hotspotOptions: { 11 includeDistance: true, 12 includeElevation: false 13 }, 14 15 showModes: { 16 COLLISION: 0, 17 AUTO: 1, 18 NOTEXT: 2, 19 HIDDEN: 3, 20 ALL: 4 21 }, 22 23 Pi180: Math.PI / 180.0, 24 Pi180Inverse: 180.0 / Math.PI, 25 26 setAdvancedMode: function(val) { 27 giga.advanced = val; 28 document.querySelector('html').classList.toggle('standard-mode', !giga.advanced); 29 if (k) { 30 k.set('state.advancedMode', giga.advanced ? 1 : 0); 31 k.set('debugmode', giga.advanced); 32 k.set('showerrors', giga.advanced); 33 }; 34 giga.config.set('advanced', giga.advanced ? 1 : 0); 35 }, 36 37 toggleAdvancedMode: function() { 38 giga.setAdvancedMode(!giga.advanced); 39 }, 40 41 toggleSidePanel: function(show) { 42 const sidePanel = document.getElementById('side-panel'), pano = document.getElementById('pano'); 43 show = hasValue(show) ? show : sidePanel.classList.contains('hidden'); 44 if (show) { 45 k.set('layer[btn_togglepanel].crop', '1536|128|128|128'); 46 k.set('layer[btn_togglepanel].onovercrop', '1536|0|128|128'); 47 sidePanel.classList.remove('hidden'); 48 pano.classList.remove('full-width'); 49 } else { 50 k.set('layer[btn_togglepanel].crop', '1664|128|128|128'); 51 k.set('layer[btn_togglepanel].onovercrop', '1664|0|128|128'); 52 sidePanel.classList.add('hidden'); 53 pano.classList.add('full-width'); 54 } 55 }, 56 57 resizeHandler: function() { 58 giga.currentPanorama.recalculateLevels(); 59 //k.call('adjust_button_sizes();'); 60 }, 61 62 sw: { 63 clearCache: function() { 64 try { 65 caches.keys().then(keys => Promise.all(keys.map(k => caches.delete(k)))); 66 } catch(e) { 67 console.log('cache clearing failed'); 68 } 69 } 70 }, 71 72 getStatistics: function() { 73 try { 74 return { 75 panoramas: panos?.length || 0, 76 hotspots: panos?.reduce((cnt, pid) => cnt + (giga.data[pid]?.length || 0), 0) || 0, 77 mpix: panos?.reduce((cnt, pid) => cnt + (Math.round(giga.sizes[pid]?.width*giga.sizes[pid]?.height/(1000000)) || 0), 0) || 0, 78 points: Array.from(hotspots.points?.values()).length || 0 79 }; 80 } catch(e) { 81 return null; 82 } 83 }, 84 85 currentPanorama: { 86 richHotspots: [], 87 loadedID: '', 88 getID: function() { 89 return k.get('state.currentpanorama'); 90 }, 91 levels: [], 92 originalLevels: [], 93 getLevel: function(fov) { 94 const lev = giga.currentPanorama.levels.findLastIndex( lev => lev.fov>=fov ); 95 return lev>=0 ? lev : -1; 96 }, 97 updateInfo: function() { 98 // console.log('updateInfo'); 99 giga.currentPanorama.levels = []; 100 k.labels.group.getArray().forEach( function(e) { 101 const level = parseInt(e.name.substring(5),10); 102 giga.currentPanorama.levels[level] = {fov: +e.fov, level: level, name: e.name}; 103 }); 104 giga.currentPanorama.originalLevels = giga.currentPanorama.levels; 105 }, 106 recalculateLevels: function() { 107 const fovmin = typeof k =='undefined' ? 1 : k.view?.fovmin; 108 if (fovmin==1) { 109 console.log('view not ready yet in recalculateLevels!',fovmin); 110 return; // view not ready yet! 111 } 112 const zoomedIn = giga.currentPanorama.levels.at(-1).fov; 113 const zoomedOut = giga.currentPanorama.levels[0].fov; 114 //console.log('fovmin: ',fovmin); 115 //console.log('zoomedIn: ',zoomedIn); 116 //console.log('zoomedOut: ',zoomedOut); 117 if (1 || fovmin>zoomedIn) { 118 //console.log('recalculating'); 119 const steps = giga.currentPanorama.levels.length; 120 const precision = 7; 121 const factor = Math.pow(zoomedOut/(fovmin*1.07), 1/(steps-1)); 122 for (let i=0; i<steps; i++) { 123 const zz = (+(zoomedOut/Math.pow(factor,i)).toFixed(precision)); 124 //console.log(i, zz); 125 giga.currentPanorama.levels[i].fov = zz; 126 //console.log(zz, fovmin, k.view.fovmin, k.get('view.fovmin')); 127 } 128 giga.updateViewInfo(); 129 k.call('doSetHotspotVisibility'); 130 } 131 }, 132 generatePTTKCode: function() { 133 const info = k.info; 134 const maxlevel = k.image.level.getArray()[k.image.level.count-1]; 135 const iwidth = +maxlevel.tiledimagewidth; 136 const iheight = +maxlevel.tiledimageheight; 137 const mpix = Math.round(iwidth*iheight/(1000000)); 138 return '<p> </p><p>GIGAPANORAMA - <a href="https://zbooy.pl/giga/view,'+giga.currentPanorama.getID()+'.html" target="_blank" rel="noopener noreferrer">'+info.title+'</a></p>'+ 139 '<p>'+mpix+' '+odmien(mpix,'megapiksel','megapiksele','megapikseli')+' ('+iwidth+'x'+iheight+')</p>'+ 140 '<p><a href="https://zbooy.pl/giga/view,'+giga.currentPanorama.getID()+'.html" target="_blank" rel="noopener noreferrer">'+ 141 '<img style="max-width: 100%;" src="https://zbooy.pl/giga/data/'+giga.currentPanorama.getID()+'/tiles/preview.jpg" alt="Miniatura panoramy - kliknij, żeby przejÅÄ do peÅnej wersji"></a></p>'+ 142 '<p>AUTOR: Szymon "Zbooy" Madej</p>'; 143 }, 144 getHotspotsList: function() { 145 return k.hotspot.getArray().filter( e => e.style?.startsWith("hst")).map( e => { 146 return { 147 id: e.id, 148 name: e.name, 149 labelname: e.labelname, 150 title: e.title, 151 ath: e.ath, 152 atv: e.atv, 153 kind: e.kind || 'peak', 154 level: e.level || 0 155 }; 156 }); 157 }, 158 getRichHotspotsList: function(doNotUseCache=false) { 159 const currentID = giga.currentPanorama.getID(); 160 if (currentID!=giga.currentPanorama.loadedID || giga.currentPanorama.richHotspots.length==0 || doNotUseCache) { 161 const hsList = giga.currentPanorama.getHotspotsList(); 162 const info = k.info; 163 giga.currentPanorama.richHotspots = hsList.map( function(h) { 164 const p = hotspots.points.get(h.id); 165 h.bearingonpan = giga.hposToAzimuth(+h.ath); 166 if (hasValue(p)) { 167 h.kind = p.kind; 168 h.elevation = p.elevation; 169 if (hasValue(p.lat, p.lon)) { 170 h.distance = giga.getDistance(info.lat,info.lon,p.lat,p.lon); 171 h.bearingreal = giga.getBearing(info.lat,info.lon,p.lat,p.lon); 172 h.bearingdiff = (h.bearingreal-h.bearingonpan); 173 } 174 } else { 175 console.warn(`hotspots.points["${h.id}"] NOT FOUND!`); 176 } 177 return h; 178 }); 179 giga.currentPanorama.loadedID = currentID; 180 } 181 return giga.currentPanorama.richHotspots; 182 }, 183 184 getHotspotDescription: function(hsi, opts) { 185 const { 186 includeDistance = giga.hotspotOptions.includeDistance, 187 includeElevation = giga.hotspotOptions.includeElevation, 188 includeBearingError = false, 189 useLongTitle = false 190 } = opts || {}; 191 192 let hhtml = useLongTitle && hsi?.longtitle ? hsi.longtitle : hsi.displaytitle ?? hsi.title; 193 if (hhtml.indexOf('|') !== -1) { 194 hhtml = hhtml.split('|').join('<br>'); 195 } 196 197 const point = hasValue(hsi.id) ? hotspots.points.get(hsi.id) : null; 198 199 if (hasValue(point)) { 200 const { kind, elevation, lat, lon } = point; 201
202 if (kind === 'town' || kind === 'valley') hhtml = hhtml.toUpperCase(); 203 204 if (includeElevation && hasValue(elevation)) { 205 hhtml += ` <span class="hsl-elev">${elevation}</span>`; 206 } 207 208 if ((includeDistance || includeBearingError) && hasValue(lat) && hasValue(lon)) { 209 const { lat: infoLat, lon: infoLon } = k.info; 210 211 if (includeDistance) { 212 const dist = giga.getDistance(+infoLat, +infoLon, +lat, +lon); 213 hhtml = `<b>${dist.toFixed(1)} km</b><br>` + hhtml; 214 } 215 216 if (includeBearingError) { 217 const bearingOnPan = giga.hposToAzimuth(+hsi.ath); 218 const bearingReal = giga.getBearing(+infoLat, +infoLon, +lat, +lon); 219 const bearingDiff = giga.plusMinus180(bearingReal - bearingOnPan); 220 const prefix = bearingDiff > 0 ? '+' : ''; 221 hhtml = `<span class="hsl-berror">${prefix}${bearingDiff.toFixed(2)}°</span> ` + hhtml; 222 } 223 } 224 } else if (hasValue(hsi.id)) { 225 //console.log('No such point: ' + hsi.id); 226 } 227 228 hhtml += `<span class="hsl-level" data-level="${hsi.level ?? 0}"></span>`; 229 return hhtml; 230 }, 231 232 getLongHotspotDescription: function(hsi, opts) { 233 const options = Object.assign({ 234 useLongTitle: true 235 }, opts); 236 return giga.currentPanorama.getHotspotDescription(hsi, options); 237 }, 238 239 generateLabels: function(opts) { 240 k.layer.getArray().filter(l => l.class == 'hotspotlabel').forEach(l => { l.html = giga.currentPanorama.getHotspotDescription(l.hs, opts); }); 241 }, 242 243 appendExtraAttribute: function(...attributes) { 244 k.layer.getArray().filter(l => l.class == 'hotspotlabel').forEach(l => { 245 const postfixes = []; 246 for (const attr of attributes) { 247 const a = hotspots.points.get(l.hs.id)?.[attr]; 248 if (a) postfixes.push(`<span class="hsl-extra">${a}</span>`); 249 } 250 l.html = giga.currentPanorama.getHotspotDescription(l.hs, {}) + postfixes.join(' '); 251 }); 252 }, 253 254 toggleHotspotsLabels: function() { 255 giga.loader.show(); 256 if (!giga.hotspotOptions.includeDistance) { 257 giga.hotspotOptions.includeDistance = true; 258 giga.hotspotOptions.includeElevation = !giga.hotspotOptions.includeElevation; 259 } else { 260 giga.hotspotOptions.includeDistance = !giga.hotspotOptions.includeDistance; 261 } 262 263 const { includeDistance, includeElevation } = giga.hotspotOptions; 264 const hsLabelDescription = (includeDistance || includeElevation) 265 ? ['nazwa', ...(includeDistance ? ['odlegÅoÅÄ'] : []), ...(includeElevation ? ['wysokoÅÄ'] : [])] 266 : ['tylko nazwa']; 267 268 showScreenMessage(`Etykiety: ${hsLabelDescription.join(' + ')}`, { category: 'hotspot-labels' }); 269 giga.currentPanorama.generateLabels({includeBearingError: 'true'===k.get('state.includeBearingError')}); 270 giga.loader.hide(); 271 }, 272 273 getObjectsList: function(sortBy) { 274 const info = k.info; 275 var hsList = giga.currentPanorama.getHotspotsList(); 276 277 // add info from points database + calculate distance & bearing errors 278 hsList = hsList.map( function(h) { 279 const p = hotspots.points.get(h.id); 280 if (hasValue(p)) { 281 h.elevation = p.elevation || ''; 282 h.range = p.range || ''; 283 h.verified = giga.isPointVerified(p); 284 h.kind = p.kind; 285 if (hasValue(p.lat, p.lon)) { 286 h.distance = giga.getDistance(info.lat,info.lon,p.lat,p.lon); 287 var bearingOnPan = giga.hposToAzimuth(+h.ath); 288 var bearingReal = giga.getBearing(info.lat,info.lon,p.lat,p.lon); 289 var bearingDiff = giga.plusMinus180(bearingReal-bearingOnPan); 290 } 291 h.bearing = bearingReal || null; 292 h.bearingError = bearingDiff || ''; 293 } 294 return h; 295 }); 296 297 var sortFunction; 298 switch(sortBy) { 299 case 'bearing': 300 sortFunction = function(a,b) { return +a.ath-+b.ath; }; 301 break; 302 case 'bearing-error': 303 sortFunction = function(a,b) { return Math.abs(+b.bearingError)-Math.abs(+a.bearingError); }; 304 break; 305 case 'distance': 306 sortFunction = function(a,b) { return +b.distance-+a.distance; }; 307 break; 308 case 'title': 309 default: 310 sortFunction = function(a,b) { return a.title.localeCompare(b.title) }; 311 break; 312 } 313 return hsList.sort(sortFunction); 314 }, 315 316 makeObjectsList: function(sortBy) { 317 const ret = giga.currentPanorama.getObjectsList(sortBy).map( function(h) { 318 let t = "", elev = "", extra = ""; 319 t = h.range || ''; 320 if (h.distance) t = h.distance.toFixed(1) + ' km' + (t?' / ':'') + t; 321 elev = h.elevation ? (' <small>'+h.elevation+' m</small>') : ''; 322 if (hasValue(h.bearingError)) extra = '<small>'+(h.bearingError>=0?'+':'')+h.bearingError.toFixed(2)+'°</small> '; 323 return '<div class="tr hs clickable '+giga.getBearingErrorClass(h.bearingError)+(h.verified?' verified':'')+(!h.distance && (h.kind=='peak' || h.kind=='saddle')?' no-coords-feature':'')+' kind-'+h.kind+'" data-name="'+h.name+'" title="'+t+'"><span class="td">'+extra+'</span><span class="td">'+h.title+elev+'</span></div>'; 324 }).join('\n'); 325 return `\n\n<div class="noborder-table tbl">\n${ret}\n</div>\n`; 326 }, 327 328 // object statistics 329 330 makeObjectsStats: function() { 331 const hsList = giga.currentPanorama.getHotspotsList(); 332 const info = k.info; 333 var stats = { 334 count: 0, 335 bearingError: {min: 1000, max: 0, avg: 0, median: 0}, 336 distance: {min: 1000, max: 0, avg: 0, median: 0} 337 }; 338 var distances = [], bearingErrors = []; 339 hsList.forEach( h => { 340 const p = hotspots.points.get(h.id); 341 if (hasValue(p?.lat, p?.lon)) { 342 stats.count++; 343 distances.push(giga.getDistance(info.lat,info.lon,p.lat,p.lon)); 344 const bearingOnPan = giga.hposToAzimuth(+h.ath); 345 //const bearingReal = giga.getBearing(info.lat,info.lon,p.lat,p.lon); 346 const bearingReal = giga.getBearingVincenty(info.lat,info.lon,p.lat,p.lon); 347 const bearingDiff = giga.plusMinus180(bearingReal-bearingOnPan); 348 bearingErrors.push(bearingDiff); 349 } 350 }); 351 if (stats.count>0) { 352 stats.distance.min = Math.min(...distances); 353 stats.distance.max = Math.max(...distances); 354 stats.distance.avg = mean(distances); 355 stats.distance.median = median(distances); 356 stats.bearingError.min = Math.min(...bearingErrors); 357 stats.bearingError.max = Math.max(...bearingErrors); 358 stats.bearingError.avg = mean(bearingErrors); 359 stats.bearingError.median = median(bearingErrors); 360 } 361 return stats; 362 }, 363 364 makeBearingErrorsChart: function() { 365 const bearingErrors = giga.currentPanorama.getObjectsList('bearing').filter(h=>h.bearingError); 366 const errs = bearingErrors.map(h=>h.bearingError); 367 const min = Math.min(...errs); 368 const max = Math.max(...errs); 369 const absminmax = Math.max(Math.abs(max),Math.abs(min)); 370 const divs = bearingErrors.map( h => { 371 let cls = '', ht = 0; 372 if (h.bearingError>=0) { 373 cls = 'positive'; 374 if (h.bearingError>0) ht = 100*(h.bearingError/absminmax); 375 } else { 376 cls = 'negative'; 377 ht = 100*(Math.abs(h.bearingError)/absminmax); 378 } 379 return `<div class="clickable hs ${cls}" title="${h.bearingError.toFixed(2)}° : ${h.bearing.toFixed(2)}° : ${h.distance.toFixed(2)} km : ${makeAttr(h.title)}" style="height:${ht.toFixed(1)}%" data-name="${h.name}"></div>`; 380 }); 381 return `<div class="errors-chart">${divs.join(' ')}</div>`; 382 }, 383 384 showBearingErrorsChart: function() { 385 document.querySelectorAll('.errors-chart-container').forEach(el => el.remove()); 386 const div = document.createElement('div'); 387 div.className='errors-chart-container'; 388 document.body.appendChild(div); 389 div.innerHTML = giga.currentPanorama.makeBearingErrorsChart(); 390 }, 391 392 hideBearingErrorsChart: function() { 393 document.querySelectorAll('.errors-chart-container').forEach(el => el.remove()); 394 }, 395 396 toggleBearingErrorsChart: function() { 397 if (document.querySelectorAll('.errors-chart-container').length>0) { 398 giga.currentPanorama.hideBearingErrorsChart(); 399 } else { 400 giga.currentPanorama.showBearingErrorsChart(); 401 } 402 }, 403 404 405 minCalculationDistance: 1.0, 406 407 calculateErrors: function(leftCorrection,rightCorrection,latCorrection,lonCorrection) { 408 leftCorrection = +leftCorrection || 0; 409 rightCorrection = +rightCorrection || 0; 410 latCorrection = +latCorrection || 0; 411 lonCorrection = +lonCorrection || 0; 412 413 const hsList = giga.currentPanorama.getHotspotsList(); 414 const info = k.info; 415 const stats = { 416 count: 0, 417 bearingError: {min: 1000, max: 0, avg: 0, median: 0} 418 }; 419 const newLeft = (+info.azimuthleft+leftCorrection) % 360; 420 const newRight = (+info.azimuthright+rightCorrection) % 360; 421 const bearingErrors = []; 422 423 hsList.forEach( function(h) { 424 const p = hotspots.points.get(h.id); 425 if (hasValue(p?.lat, p?.lon) ) { 426 const distance = giga.getDistance(+info.lat,+info.lon,p.lat,p.lon); 427 if (distance>=giga.currentPanorama.minCalculationDistance) { 428 stats.count++; 429 const bearingOnPan = giga.hposToAzimuthGeneric(+h.ath,newLeft,newRight,1); 430 const bearingReal = giga.getBearing(+info.lat+latCorrection,+info.lon+lonCorrection,p.lat,p.lon); 431 const bearingDiff = giga.plusMinus180(bearingReal-bearingOnPan); 432 bearingErrors.push(bearingDiff); 433 } 434 } 435 }); 436 437 if (stats.count>0) { 438 stats.bearingError.lat = +info.lat+latCorrection; 439 stats.bearingError.lon = +info.lon+lonCorrection; 440 stats.bearingError.left = newLeft; 441 stats.bearingError.right = newRight; 442 stats.bearingError.min = Math.min(...bearingErrors); 443 stats.bearingError.max = Math.max(...bearingErrors); 444 stats.bearingError.avg = mean(bearingErrors); 445 stats.bearingError.median = median(bearingErrors); 446 } 447 return stats; 448 }, 449 450 correctRange: function(steps,step,latCorrection=0,lonCorrection=0) { 451 var errors = []; 452 for(let l=-steps; l<=steps; l++) { 453 for(let r=-steps; r<=steps; r++) { 454 errors.push(giga.currentPanorama.calculateErrors(l*step,r*step,+latCorrection,+lonCorrection).bearingError); 455 } 456 } 457 /* 458 errors.sort( (a,b) => Math.abs(a.avg) - Math.abs(b.avg) ); 459 console.log('by average error'); 460 for(i=1;i<=5;i++) console.log('('+errors[i].left.toFixed(2)+'...'+errors[i].right.toFixed(2)+')','avg',errors[i].avg.toFixed(2),'median',errors[i].median.toFixed(2),'min',errors[i].min.toFixed(2),'max',errors[i].max.toFixed(2)); 461 console.log('by abs(max error)'); 462 errors.sort( (a,b) => Math.max(Math.abs(a.min),Math.abs(a.max)) - Math.max(Math.abs(b.min),Math.abs(b.max)) ); 463 for(i=1;i<=5;i++) console.log('('+errors[i].left.toFixed(2)+'...'+errors[i].right.toFixed(2)+')','avg',errors[i].avg.toFixed(2),'median',errors[i].median.toFixed(2),'min',errors[i].min.toFixed(2),'max',errors[i].max.toFixed(2)); 464 */ 465 return errors; 466 }, 467 468 correctPosition: function(steps,step,minDistance=0) { 469 giga.currentPanorama.minCalculationDistance = minDistance; 470 var errors = []; 471 for (let l=-steps; l<=steps; l++) { 472 for(let r=-steps; r<=steps; r++) { 473 const e = giga.currentPanorama.correctRange(5,0.01, l*step,r*step); 474 e.sort( (a,b) => Math.abs(a.avg) - Math.abs(b.avg) ); 475 errors.push(e[0]); 476 } 477 } 478 console.log('by average error'); 479 errors.sort( (a,b) => Math.abs(a.avg) - Math.abs(b.avg) ); 480 for (let i=1;i<=5;i++) console.log(errors[i].lat.toFixed(5),errors[i].lon.toFixed(5), '('+errors[i].left.toFixed(2)+'...'+errors[i].right.toFixed(2)+')','avg',errors[i].avg.toFixed(2),'median',errors[i].median.toFixed(2),'min',errors[i].min.toFixed(2),'max',errors[i].max.toFixed(2)); 481 console.log('by abs(max error)'); 482 errors.sort( (a,b) => Math.max(Math.abs(a.min),Math.abs(a.max)) - Math.max(Math.abs(b.min),Math.abs(b.max)) ); 483 for (let i=1;i<=5;i++) console.log(errors[i].lat.toFixed(5),errors[i].lon.toFixed(5), '('+errors[i].left.toFixed(2)+'...'+errors[i].right.toFixed(2)+')','avg',errors[i].avg.toFixed(2),'median',errors[i].median.toFixed(2),'min',errors[i].min.toFixed(2),'max',errors[i].max.toFixed(2)); 484 }, 485 486 487 optimizeCentralPoint: (initialCentral, points) => { 488 // initialCentral: {lat: number, lon: number} 489 // points: [{lat:number, lon:number, bearing:number}] 490 491 const info = k.info; 492 initialCentral ??= { lat: +info.lat, lon: +info.lon };
493 points ??= giga.currentPanorama.getHotspotsList().map( h => { 494 return { ...hotspots.points.get(h.id), ath: +h.ath } 495 }).filter( p => 496 hasValue(p?.lat, p?.lon) && giga.getDistance(+info.lat,+info.lon,p.lat,p.lon) >= giga.currentPanorama.minCalculationDistance 497 ).map( p => { 498 return { lat: p.lat, lon: p.lon, bearing: giga.hposToAzimuthGeneric(+p.ath,+info.azimuthleft,+info.azimuthright,1) } 499 }); 500 501 let central = { ...initialCentral }; 502 const learningRate = 0.0001; // small step size 503 const maxIterations = 10000; //10000 504 const tolerance = 1e-9; 505 506 console.log(central); 507 console.log(points); 508 509 function computeError(center) { 510 let error = 0; 511 for (const pt of points) { 512 const calcBearing = (giga.getBearing(center.lat, center.lon, pt.lat, pt.lon) + 360) % 360; 513 let diff = calcBearing - pt.bearing; 514 //console.log(diff); 515 if (diff > 180) diff -= 360; 516 if (diff < -180) diff += 360; 517 error += diff * diff; 518 } 519 return error; 520 } 521 522 // Numerical gradient approximation for latitude and longitude 523 function computeGradient(center) { 524 const h = 1e-6; 525 const err = computeError(center); 526 527 const latUp = { lat: center.lat + h, lon: center.lon }; 528 const errLatUp = computeError(latUp); 529 const dErrLat = (errLatUp - err) / h; 530 531 const lonUp = { lat: center.lat, lon: center.lon + h }; 532 const errLonUp = computeError(lonUp); 533 const dErrLon = (errLonUp - err) / h; 534 535 return { dLat: dErrLat, dLon: dErrLon }; 536 } 537 538 let prevError = computeError(central); 539 540 for (let i = 0; i < maxIterations; i++) { 541 const showIter = (i % 200 == 0); 542 if (showIter) console.log(`iteration ${i}`); 543 const grad = computeGradient(central); 544 545 // Update central point coordinates 546 central.lat -= learningRate * grad.dLat; 547 central.lon -= learningRate * grad.dLon; 548 549 const currentError = computeError(central); 550 if (showIter) console.log(currentError); 551 552 if (Math.abs(prevError - currentError) < tolerance) { 553 prevError = currentError; 554 break; // Stop if error improvement is below tolerance 555 } 556 prevError = currentError; 557 } 558 console.log(prevError); 559 560 return central; 561 }, 562 563 /* 564 // Example usage: 565 const initialCentral = { lat: 50.0, lon: 20.0 }; 566 const points = [ 567 { lat: 50.1, lon: 20.1, bearing: 45 }, 568 { lat: 50.2, lon: 19.9, bearing: 315 }, 569 { lat: 49.9, lon: 20.2, bearing: 90 }, 570 ]; 571 572 const optimizedCentral = optimizeCentralPoint(initialCentral, points); 573 console.log('Optimized Central Point:', optimizedCentral); 574 */ 575 576 formatObjectsStats: function(stats) { 577 var s = ''; 578 s += `<div class="ind"><i class="fas fa-fw fa-thumbtack"></i> ${stats.count} ${odmien(stats.count,'uwzglÄdniony','uwzglÄdnione','uwzglÄdnionych')}</div> 579 <div class="ind" title="odlegÅoÅÄ do obiektów (Årednia/mediana, zakres)"><i class="fas fa-fw fa-binoculars"></i> ${stats.distance.avg.toFixed(1)}/${stats.distance.median.toFixed(1)} km (${stats.distance.min.toFixed(1)}...${stats.distance.max.toFixed(1)} km)</div> 580 <div class="ind" title="odchylenia azymutu (Årednia/mediana, zakres)"><i class="fas fa-fw fa-arrows-alt-h"></i> ${addSign(stats.bearingError.avg.toFixed(2))}/${addSign(stats.bearingError.median.toFixed(2))}° (${addSign(stats.bearingError.min.toFixed(2))}...${addSign(stats.bearingError.max.toFixed(2))}°)</div>`; 581 return s; 582 }, 583 584 getMinMaxElevationAngles: function() { 585 const info = k.info; 586 const ang = giga.currentPanorama.getHotspotsList().filter( (h) => { let p = hotspots.points.get(h.id); return !!p && !!p?.lat && !!p?.lon && !!p?.elevation; } ).map( (h) => { 587 let p = hotspots.points.get(h.id); 588 return giga.getElevationAngle(+info.lat,+info.lon,+info.elevation, +p.lat,+p.lon,+p.elevation); 589 }).sort((a,b)=>a-b); 590 return [ang[0],ang[ang.length-1]]; 591 }, 592 593 interpolateVerticalRange: function() { 594 console.log('interpolating...'); 595 const info = k.info, image = k.image; 596 const allAngles = giga.currentPanorama.getHotspotsList().filter( (h) => { let p = hotspots.points.get(h.id); return !!p && !!p?.lat && !!p?.lon && !!p?.elevation; } ).map( (h) => { 597 let p = hotspots.points.get(h.id); 598 return [+h.atv, giga.getElevationAngle(+info.lat,+info.lon,+info.elevation, +p.lat,+p.lon,+p.elevation)]; 599 }).sort((a,b)=>a[1]-b[1]); 600 if (allAngles.length==0) return null; 601 let angles = [allAngles[0],allAngles[Math.round(allAngles.length/2)],allAngles[allAngles.length-1]]; 602 //console.log(angles); 603 let ip = interpolatingPolynomial(angles); 604 console.log('top: ',-image.vfov,-ip(-image.vfov/2)); 605 console.log('bot: ',+image.vfov,-ip(+image.vfov/2)); 606 return { 607 azimuthtop: -ip(-image.vfov/2).toFixed(2), 608 azimuthbottom: -ip(+image.vfov/2).toFixed(2) 609 } 610 }, 611 612 multiSelect: { 613 points: [], 614 infowindow: null, 615 616 add: function(h) { 617 const p = hotspots.points.get(h?.id); 618 if (!hasValue(p?.lat,p?.lon)) return false;
619 620 giga.currentPanorama.multiSelect.points.push(h); 621 if (!giga.currentPanorama.multiSelect.infowindow) { 622 const el = document.createElement("div"); 623 el.classList.add("popup-info-window"); 624 giga.currentPanorama.multiSelect.infowindow = el; 625 document.getElementById('content').appendChild(el); 626 } 627 giga.currentPanorama.multiSelect.infowindow.innerHTML = giga.currentPanorama.multiSelect.getText() + '<div class="close" title="Zamknij" onclick="giga.currentPanorama.multiSelect.clear()">X</div>'; 628 giga.currentPanorama.multiSelect.drawLine(); 629 }, 630 631 clear: function() { 632 giga.currentPanorama.multiSelect.points = []; 633 giga.currentPanorama.multiSelect.drawLine(); 634 if (giga.currentPanorama.multiSelect.infowindow) { 635 giga.currentPanorama.multiSelect.infowindow.remove(); 636 giga.currentPanorama.multiSelect.infowindow = null; 637 } 638 }, 639 640 getText: function() { 641 var prevPoint, cumulativeDistance = 0; 642 var ret = giga.currentPanorama.multiSelect.points.map( (h,i) => { 643 //console.log(h,i); 644 let p = hotspots.points.get(h.id); 645 let dist = 0, bear; 646 if (hasValue(prevPoint, p?.lat,p?.lon)) { 647 dist = giga.getDistance(prevPoint.lat,prevPoint.lon,p.lat,p.lon); 648 bear = giga.getBearing(prevPoint.lat,prevPoint.lon,p.lat,p.lon); 649 cumulativeDistance += dist; 650 } 651 if (hasValue(p?.lat,p?.lon)) prevPoint = p; 652 return `<tr><td align="right">${i+1}</td><td><b>${p.names[0]}</b></td><!--<td>${p?.lat}</td><td>${p?.lon}</td>--><td align="right" title="azymut od poprzedniego punktu">${bear ? (bear.toFixed(2)+'°') : ''}</td><td align="right" title="odlegÅoÅÄ od poprzedniego punktu"><b>${dist.toFixed(3)} km</b></td><td align="right" title="sumaryczna odlegÅoÅÄ od pierwszego punktu"><b>${cumulativeDistance.toFixed(3)} km</b></td></tr>`; 653 }).join(''); 654 return `<table>${ret}</table>`; 655 }, 656 657 drawLine: function () { 658 try { 659 k.removehotspot('distanceline'); 660 } catch (e) {} 661 if (giga.currentPanorama.multiSelect.points.length>0) { 662 const dline = k.addhotspot('distanceline'); 663 dline.loadstyle('distline'); 664 dline.points = giga.currentPanorama.multiSelect.points.map(hs => `${hs.ath},${hs.atv}`).join(','); 665 //dline.polyline = true; 666 //dline.fillcolor = "0xbb4400"; 667 //dline.fillalpha = 0.7; 668 //dline.borderwidth = 4.0; 669 670 } 671 }, 672 673 show: function() { 674 } 675 }, 676 677 678 // overlay maps 679 680 overlayMap: { 681 map: undefined, 682 semi: undefined, 683 point: undefined, 684 685 make: function(mapContainer) { 686 const info = k.info; 687 const latlng = [+info.lat, +info.lon]; 688 const hsList = giga.currentPanorama.getHotspotsList().filter( (h) => { 689 let p = hotspots.points.get(h.id); 690 return hasValue(p?.lat, p?.lon); 691 }).sort( (a, b) => +a.ath >= +b.ath ); 692 693 const mymap = L.map(mapContainer, { 694 zoomControl: false 695 }).addControl(L.control.zoom({ 696 zoomInTitle: 'Przybliż', 697 zoomOutTitle: 'Oddal', 698 zoomSnap: 0.5, 699 zoomDelta: 0.5 700 })).setView(latlng, 10); 701 //mymap.createPane('view'); 702 //mymap.getPane('view').style.zIndex = 350; 703 704 L.tileLayer('https://api.mapbox.com/styles/v1/zbooyek/cjb3mj9ja03ru2sp7l0x32j60/tiles/256/{z}/{x}/{y}?access_token=pk.eyJ1IjoiemJvb3llayIsImEiOiJjajF4OHdmZHMwMTY0MzNta2VvYnY3d2RxIn0.KA0JiliY1oiTuzFustFIaA', { 705 attribution: 'Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://mapbox.com">Mapbox</a>', 706 maxZoom: 18, 707 id: 'mapbox.streets', 708 accessToken: 'pk.eyJ1IjoiemJvb3llayIsImEiOiJjajF4OHdmZHMwMTY0MzNta2VvYnY3d2RxIn0.KA0JiliY1oiTuzFustFIaA' 709 }).addTo(mymap); 710 711 const aStart = +info.azimuthleft; 712 const aStop = +info.azimuthright; 713 var range, direction; 714 if (aStop>=aStart) { 715 range = aStop-aStart; 716 direction = aStart + range/2; 717 } else { 718 range = aStop+360-aStart; 719 direction = aStart + range/2; 720 if (direction>=360) direction -= 360; 721 } 722 723 const wholeView = L.semiCircle(latlng, { 724 interactive: false, 725 fill: true, 726 fillColor:'#444444', 727 fillOpacity: 0.11, 728 color: '#444444', 729 opacity: 0.0, 730 sticky: true, 731 radius: info.range ? +info.range*1000 : 100000 732 }).setDirection(+direction,+range).addTo(mymap); 733 734 L.control.mousePosition({ 735 latFormatter: n => n.toFixed(5), 736 lngFormatter: n => n.toFixed(5), 737 position: 'topright', 738 separator: ', ', 739 emptyString: '---' 740 }).addTo(mymap); 741 742 const lines = L.featureGroup(); 743 hsList.map( function(h) { 744 let p = hotspots.points.get(h.id); 745 L.polyline([latlng, [+p.lat,+p.lon]], { 746 keyboard: false, 747 title: p.name, 748 color: '#ff0000', 749 opacity: 0.66, 750 weight: 2 751 }).on('click', function() { 752 giga.moveToHotspot(h.name); 753 }).bindTooltip(h.title, { 754 keyboard: false, 755 interactive: false, 756 sticky: true 757 }).addTo(lines); 758 }); 759 lines.addTo(mymap); 760 mymap.fitBounds(lines.getBounds()); 761 762 L.circleMarker(latlng, { 763 radius: 3, 764 fill: '#00aa00', 765 color: '#00aa00' 766 }).addTo(mymap); 767 768 $('#'+mapContainer).parents('.resizable').on("resizestop", function( event, ui ) { 769 console.log('resizestop handler'); 770 window.setTimeout( function() { 771 mymap.invalidateSize({ 772 pan: false, 773 debounceMoveend: true 774 }); 775 }, 50); 776 }); 777 778 return mymap; 779 }, 780 781 center: function(lat,lon) { 782 giga.currentPanorama.overlayMap.map?.panTo([+lat,+lon]); 783 }, 784 785 show: function() { 786 document.getElementById('overlay-map').classList.remove('hidden'); 787 if (!giga.currentPanorama.overlayMap.map) { 788 giga.currentPanorama.overlayMap.map = giga.currentPanorama.overlayMap.make('overlay-map-inside'); 789 } 790 giga.currentPanorama.overlayMap.updateView(giga.getCurrentViewParameters()); 791 }, 792 793 hide: function() { 794 document.getElementById('overlay-map').classList.add('hidden'); 795 }, 796 797 toggle: function() { 798 if (document.getElementById('overlay-map').classList.contains('hidden')) { 799 giga.currentPanorama.overlayMap.show(); 800 } else { 801 giga.currentPanorama.overlayMap.hide(); 802 } 803 }, 804 805 remove: function() { 806 if (giga.currentPanorama.overlayMap.map) { 807 giga.currentPanorama.overlayMap.map.remove(); 808 giga.currentPanorama.overlayMap.map = giga.currentPanorama.overlayMap.semi = undefined; 809 } 810 document.getElementById('overlay-map').classList.add('hidden'); 811 }, 812 813 updateView: function(currentView) { 814 if (!giga.currentPanorama.overlayMap.map) return; 815 const info = k.info; 816 const latlng = [+info.lat, +info.lon]; 817 if (!hasValue(giga.currentPanorama.overlayMap.semi)) { 818 giga.currentPanorama.overlayMap.semi = L.semiCircle(latlng, { 819 interactive: false, 820 fill: true, 821 fillColor:'#00cc22', 822 fillOpacity: 0.22, 823 color: '#00cc22', 824 opacity: 0.0, 825 sticky: true, 826 radius: info.range ? +info.range*1000 : 100000 827 }).addTo(giga.currentPanorama.overlayMap.map).bringToFront(); 828 } 829 giga.currentPanorama.overlayMap.semi.setDirection(+currentView.center,+currentView.range); 830 }, 831 832 showPoint: function(lat,lon,title='') { 833 if (!giga.currentPanorama.overlayMap.map) return; 834 giga.currentPanorama.overlayMap.removePoint(); 835 giga.currentPanorama.overlayMap.point = L.circleMarker([lat,lon], { 836 radius: 3, 837 title: title, 838 zIndexOffset: 100, 839 fill: '#0000ff', 840 color: '#0000ff' 841 }).addTo(giga.currentPanorama.overlayMap.map); 842 }, 843 844 removePoint: function() { 845 if (giga.currentPanorama.overlayMap.point) { 846 giga.currentPanorama.overlayMap.point.remove(); 847 giga.currentPanorama.overlayMap.point = undefined; 848 } 849 } 850 } 851 }, 852 853 getBearingErrorClass(bearingDiff) { 854 var bearingColorClass = ''; 855 if (Math.abs(bearingDiff)>=0.4) { 856 bearingColorClass = 'red'; 857 } else if (Math.abs(bearingDiff)>=0.2) { 858 bearingColorClass = 'yellow'; 859 } 860 return bearingColorClass; 861 }, 862 863 kindToString: function(kind) { 864 switch(kind) { 865 case 'peak': return 'szczyt'; 866 case 'col': return 'przeÅÄcz'; 867 case 'saddle': return 'przeÅÄcz'; 868 case 'town': return 'miejscowoÅÄ'; 869 case 'lake': return 'jezioro'; 870 case 'valley': return 'dolina'; 871 case 'glade': return 'polana'; 872 case 'glacier': return 'lodowiec'; 873 case 'forest': return 'las'; 874 case 'ridge': return 'grzbiet'; 875 case 'formation': return 'formacja'; 876 case 'building': return 'konstrukcja'; 877 case 'island': return 'wyspa'; 878 case 'river': return 'rzeka'; 879 case 'canal': return 'kanaÅ'; 880 case 'waterfall': return 'wodospad'; 881 case 'road': return 'droga'; 882 case 'smokestack': return 'komin'; 883 case 'chimney': return 'komin'; 884 case 'mineshaft': return 'szyb kopalni'; 885 case 'mast': return 'maszt'; 886 } 887 return `? (${kind})`; 888 }, 889 890 // To convert radians to degrees, divide by (Math.PI/180), and multiply by this to convert the other way. 891 deg2rad: function(d) { 892 return d * giga.Pi180; 893 }, 894 rad2deg: function(d) { 895 return d * giga.Pi180Inverse; 896 }, 897 898 getDistanceCosine: function(startLat,startLong,endLat,endLong) {
899 const endRad = giga.deg2rad(+endLat), startRad = giga.deg2rad(+startLat); 900 const theta = startLong -endLong; 901 const d = Math.sin(startRad) * Math.sin(endRad) + Math.cos(startRad) * Math.cos(endRad) * Math.cos(giga.deg2rad(theta)); 902 return giga.rad2deg(Math.acos(d)) * 60 * 1.1515 * 1.609344; 903 }, 904 haversine: function (x) { 905 const sx = Math.sin(x/2); 906 return sx * sx; 907 }, 908 getDistance: function (startLat,startLong,endLat,endLong) { 909 const endRad = giga.deg2rad(+endLat), startRad = giga.deg2rad(+startLat); 910 const ht = giga.haversine(endRad - startRad) + Math.cos(startRad) * Math.cos(endRad) * giga.haversine(giga.deg2rad(+endLong) - giga.deg2rad(+startLong)); 911 return 12756.274 * Math.asin(Math.sqrt(ht)); 912 }, 913 914 wrap360: function(degrees) { 915 if (0<=degrees && degrees<360) return degrees; // avoid rounding due to arithmetic ops if within range 916 // bearing wrapping requires a sawtooth wave function with a vertical offset equal to the 917 // amplitude and a corresponding phase shift; this changes the general sawtooth wave function from 918 // f(x) = (2ax/p - p/2) % p - a 919 // to 920 // f(x) = (2ax/p) % p 921 // where a = amplitude, p = period, % = modulo; however, JavaScript '%' is a remainder operator 922 // not a modulo operator - for modulo, replace 'x%n' with '((x%n)+n)%n' 923 const x = degrees, a = 180, p = 360; 924 return (((2*a*x/p)%p)+p)%p; 925 }, 926 927 plusMinus180: function(degrees) { 928 if (degrees>180.0) { 929 degrees -= 360.0; 930 } else if (degrees<-180.0) { 931 degrees += 360.0; 932 } 933 return degrees; 934 }, 935 936 vincentyInverse: function(startLat, startLon, endLat, endLon) { 937 const a = 6378137, b = 6356752.3142, f = 1 / 298.257223563; 938 const f1 = 1 - f, a2mb2 = a*a - b*b, b2 = b*b; 939 const Pi = Math.PI, Eps = Number.EPSILON; 940 941 const Phi1 = giga.deg2rad(startLat), Phi2 = giga.deg2rad(endLat); 942 const L = giga.deg2rad(endLon - startLon); 943 944 const tanU1 = f1 * Math.tan(Phi1), cosU1 = 1 / Math.sqrt(1 + tanU1*tanU1), sinU1 = tanU1 * cosU1; 945 const tanU2 = f1 * Math.tan(Phi2), cosU2 = 1 / Math.sqrt(1 + tanU2*tanU2), sinU2 = tanU2 * cosU2; 946 const cosU1cosU2 = cosU1 * cosU2, sinU1sinU2 = sinU1 * sinU2, cosU1sinU2 = cosU1 * sinU2, sinU1cosU2 = sinU1 * cosU2; 947 948 const antipodal = Math.abs(L) > Pi/2 || Math.abs(Phi2 - Phi1) > Pi/2; 949 950 let Lambda = L, LambdaPrim; 951 let sinLambda, cosLambda; 952 let sinSigma = 0, cosSigma = antipodal ? -1 : 1, Sigma = antipodal ? Pi : 0; 953 let sinSqSigma = null, cos2Sigma_m = 1, cosSqAlpha = 1; 954 let iterations = 0; 955 956 do { 957 sinLambda = Math.sin(Lambda); 958 cosLambda = Math.cos(Lambda); 959 const t = cosU1sinU2 - sinU1cosU2 * cosLambda; 960 sinSqSigma = (cosU2 * sinLambda) ** 2 + t * t; 961 if (Math.abs(sinSqSigma) < 1e-24) break; 962 sinSigma = Math.sqrt(sinSqSigma); 963 cosSigma = sinU1sinU2 + cosU1cosU2 * cosLambda; 964 Sigma = Math.atan2(sinSigma, cosSigma); 965 const sinAlpha = cosU1cosU2 * sinLambda / sinSigma; 966 cosSqAlpha = 1 - sinAlpha * sinAlpha; 967 cos2Sigma_m = cosSqAlpha !== 0 ? cosSigma - 2 * sinU1sinU2 / cosSqAlpha : 0; 968 const C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 969 LambdaPrim = Lambda; 970 Lambda = L + (1 - C) * f * sinAlpha * (Sigma + C * sinSigma * (cos2Sigma_m + C * cosSigma * (-1 + 2 * cos2Sigma_m * cos2Sigma_m))); 971 if ((antipodal ? Math.abs(Lambda) - Pi : Math.abs(Lambda)) > Pi) throw new EvalError('Lambda > Pi'); 972 } while (Math.abs(Lambda - LambdaPrim) > 1e-12 && ++iterations < 1000); 973 974 if (iterations >= 1000) throw new EvalError('Vincenty formula failed to converge'); 975 976 const uSq = cosSqAlpha * a2mb2 / b2; 977 const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 978 const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 979 const ss2 = sinSigma * sinSigma; 980 const c2sm2 = cos2Sigma_m * cos2Sigma_m; 981 const DeltaSigma = B * sinSigma * (cos2Sigma_m + B / 4 * (cosSigma * (-1 + 2 * c2sm2) - B / 6 * cos2Sigma_m * (-3 + 4 * ss2) * (-3 + 4 * c2sm2))); 982 const s = b * A * (Sigma - DeltaSigma); 983 984 const tiny = Math.abs(sinSqSigma) < Eps;
985 const Alpha1 = tiny ? 0 : Math.atan2(cosU2 * sinLambda, cosU1sinU2 - sinU1cosU2 * cosLambda); 986 const Alpha2 = tiny ? Pi : Math.atan2(cosU1 * sinLambda, -sinU1cosU2 + cosU1sinU2 * cosLambda); 987 const nanDist = Math.abs(s) < Eps; 988 989 return { 990 distance: s, 991 initialBearing: nanDist ? NaN : giga.wrap360(giga.rad2deg(Alpha1)), 992 finalBearing: nanDist ? NaN : giga.wrap360(giga.rad2deg(Alpha2)), 993 iterations, 994 }; 995 }, 996 997 vincentyInverseOLD: function (startLat,startLong,endLat,endLong) { 998 const Phi1 = giga.deg2rad(startLat), Lambda1 = giga.deg2rad(startLong); 999 const Phi2 = giga.deg2rad(endLat), Lambda2 = giga.deg2rad(endLong); 1000 const Pi = Math.PI, Epsilon = Number.EPSILON; 1001 1002 const a = 6378137, b = 6356752.3142, f = 1 / 298.257223563; // WGS-84 ellipsoid params 1003 1004 const L = Lambda2 - Lambda1; // L = difference in longitude, U = reduced latitude, defined by tan U = (1-f)·tanPhi. 1005 const tanU1 = (1-f) * Math.tan(Phi1), cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1 * cosU1; 1006 const tanU2 = (1-f) * Math.tan(Phi2), cosU2 = 1 / Math.sqrt((1 + tanU2*tanU2)), sinU2 = tanU2 * cosU2; 1007 1008 const antipodal = Math.abs(L) > Pi/2 || Math.abs(Phi2-Phi1) > Pi/2; 1009 1010 let Lambda = L, sinLambda = null, cosLambda = null; // Lambda = difference in longitude on an auxiliary sphere 1011 let Sigma = antipodal ? Pi : 0, sinSigma = 0, cosSigma = antipodal ? -1 : 1, sinSqSigma = null; // Sigma = angular distance Pâ Pâ on the sphere 1012 let cos2Sigma_m = 1; // Sigma_m = angular distance on the sphere from the equator to the midpoint of the line 1013 let cosSqAlpha = 1; // Alpha = azimuth of the geodesic at the equator 1014 1015 let LambdaPrim = null, iterations = 0; 1016 do { 1017 sinLambda = Math.sin(Lambda); 1018 cosLambda = Math.cos(Lambda); 1019 sinSqSigma = (cosU2*sinLambda)**2 + (cosU1*sinU2-sinU1*cosU2*cosLambda)**2; 1020 if (Math.abs(sinSqSigma) < 1e-24) break; // co-incident/antipodal points (Sigma < â0.006mm) 1021 sinSigma = Math.sqrt(sinSqSigma); 1022 cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda; 1023 Sigma = Math.atan2(sinSigma, cosSigma); 1024 const sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; 1025 cosSqAlpha = 1 - sinAlpha*sinAlpha; 1026 cos2Sigma_m = (cosSqAlpha != 0) ? (cosSigma - 2*sinU1*sinU2/cosSqAlpha) : 0; // on equatorial line cos²Alpha = 0 (paragraph 6) 1027 const C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); 1028 LambdaPrim = Lambda; 1029 Lambda = L + (1-C) * f * sinAlpha * (Sigma + C*sinSigma*(cos2Sigma_m+C*cosSigma*(-1+2*cos2Sigma_m*cos2Sigma_m))); 1030 const iterationCheck = antipodal ? Math.abs(Lambda)-Pi : Math.abs(Lambda); 1031 if (iterationCheck > Pi) throw new EvalError('Lambda > Pi'); 1032 } while (Math.abs(Lambda-LambdaPrim) > 1e-12 && ++iterations<1000); // TV: 'iterate until negligible change in Lambda' (â0.006mm) 1033 if (iterations >= 1000) throw new EvalError('Vincenty formula failed to converge'); 1034 1035 const uSq = cosSqAlpha * (a*a - b*b) / (b*b); 1036 const A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); 1037 const B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); 1038 const DeltaSigma = B*sinSigma*(cos2Sigma_m+B/4*(cosSigma*(-1+2*cos2Sigma_m*cos2Sigma_m)-B/6*cos2Sigma_m*(-3+4*sinSigma*sinSigma)*(-3+4*cos2Sigma_m*cos2Sigma_m))); 1039 1040 const s = b*A*(Sigma-DeltaSigma); // s = length of the geodesic 1041 1042 // note special handling of exactly antipodal points where sin²Sigma = 0 (due to discontinuity 1043 // atan2(0, 0) = 0 but atan2(Epsilon, 0) = Pi/2 / 90°) - in which case bearing is always meridional, 1044 // due north (or due south!) 1045 // Alpha = azimuths of the geodesic; Alpha2 the direction Pâ Pâ produced 1046 const Alpha1 = Math.abs(sinSqSigma) < Epsilon ? 0 : Math.atan2(cosU2*sinLambda, cosU1*sinU2-sinU1*cosU2*cosLambda); 1047 const Alpha2 = Math.abs(sinSqSigma) < Epsilon ? Pi : Math.atan2(cosU1*sinLambda, -sinU1*cosU2+cosU1*sinU2*cosLambda); 1048 1049 return { 1050 distance: s, 1051 initialBearing: Math.abs(s) < Epsilon ? NaN : giga.wrap360(giga.rad2deg(Alpha1)), 1052 finalBearing: Math.abs(s) < Epsilon ? NaN : giga.wrap360(giga.rad2deg(Alpha2)), 1053 iterations: iterations, 1054 }; 1055 }, 1056 1057 getDistanceVincenty: function(startLat,startLong,endLat,endLong) { 1058 return giga.vincentyInverse(startLat,startLong,endLat,endLong).distance / 1000; 1059 }, 1060 1061 getDistanceVincenty3D: function(lat1, lon1, elev1, lat2, lon2, elev2) { 1062 // WGS84 ellipsoid parameters 1063 const a = 6378137.0; // Semi-major axis (equatorial radius) in meters 1064 const f = 1 / 298.257223563; // Flattening 1065 const b = a * (1 - f); // Semi-minor axis (polar radius) 1066 1067 // Convert degrees to radians 1068 const lat1Rad = lat1*giga.Pi180; 1069 const lat2Rad = lat2*giga.Pi180; 1070 const lon1Rad = lon1*giga.Pi180; 1071 const lon2Rad = lon2*giga.Pi180; 1072 1073 const L = lon2Rad - lon1Rad; 1074 1075 const U1 = Math.atan((1 - f) * Math.tan(lat1Rad)); 1076 const U2 = Math.atan((1 - f) * Math.tan(lat2Rad)); 1077 1078 const sinU1 = Math.sin(U1); 1079 const cosU1 = Math.cos(U1); 1080 const sinU2 = Math.sin(U2);
1081 const cosU2 = Math.cos(U2); 1082 1083 let lambda = L; 1084 let lambdaP; 1085 let iterLimit = 100; 1086 let cosSqAlpha, sinSigma, cos2SigmaM, cosSigma, sigma; 1087 1088 do { 1089 const sinLambda = Math.sin(lambda); 1090 const cosLambda = Math.cos(lambda); 1091 1092 sinSigma = Math.sqrt( 1093 (cosU2 * sinLambda) * (cosU2 * sinLambda) + 1094 (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * 1095 (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) 1096 ); 1097 1098 if (sinSigma === 0) { 1099 return Math.abs(elev2 - elev1); // Co-incident points 1100 } 1101 1102 cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; 1103 sigma = Math.atan2(sinSigma, cosSigma); 1104 1105 const sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; 1106 cosSqAlpha = 1 - sinAlpha * sinAlpha; 1107 1108 cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha; 1109 if (isNaN(cos2SigmaM)) { 1110 cos2SigmaM = 0; // Equatorial line 1111 } 1112 1113 const C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 1114 1115 lambdaP = lambda; 1116 lambda = L + (1 - C) * f * sinAlpha * ( 1117 sigma + C * sinSigma * ( 1118 cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) 1119 ) 1120 ); 1121 1122 } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0); 1123 1124 if (iterLimit === 0) { 1125 return NaN; // Formula failed to converge 1126 } 1127 1128 const uSq = cosSqAlpha * (a * a - b * b) / (b * b); 1129 const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 1130 const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 1131 1132 const deltaSigma = B * sinSigma * ( 1133 cos2SigmaM + B / 4 * ( 1134 cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - 1135 B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * 1136 (-3 + 4 * cos2SigmaM * cos2SigmaM) 1137 ) 1138 ); 1139 1140 const s = b * A * (sigma - deltaSigma); 1141 1142 // Calculate elevation difference
1143 const elevDiff = elev2 - elev1; 1144 1145 // Calculate 3D distance using Pythagorean theorem 1146 return Math.sqrt(s * s + elevDiff * elevDiff) / 1000; 1147 }, 1148 1149 getBearing: function(startLat,startLong,endLat,endLong) { 1150 startLat = giga.deg2rad(startLat); 1151 startLong = giga.deg2rad(startLong); 1152 endLat = giga.deg2rad(endLat); 1153 endLong = giga.deg2rad(endLong); 1154 1155 const y = Math.sin(endLong-startLong) * Math.cos(endLat); 1156 const x = Math.cos(startLat)*Math.sin(endLat) - Math.sin(startLat)*Math.cos(endLat)*Math.cos(endLong-startLong); 1157 return (giga.rad2deg(Math.atan2(y,x)) + 360) % 360; // in degrees 1158 }, 1159 1160 getBearingVincenty: function (startLat, startLng, endLat, endLng) { 1161 const startLatRad = giga.deg2rad(startLat), startLngRad = giga.deg2rad(startLng); 1162 const endLatRad = giga.deg2rad(endLat), endLngRad = giga.deg2rad(endLng); 1163 1164 // WGS84 ellipsoid parameters 1165 const a = 6378137; // Earth semi-major axis in meters 1166 const b = 6356752.3142; // Earth semi-minor axis in meters 1167 1168 // Flattening 1169 const f = (a - b) / a; 1170 1171 // Calculate auxiliary values 1172 const U1 = Math.atan((1 - f) * Math.tan(startLatRad)); 1173 const U2 = Math.atan((1 - f) * Math.tan(endLatRad)); 1174 const sinU1 = Math.sin(U1); 1175 const cosU1 = Math.cos(U1); 1176 const sinU2 = Math.sin(U2); 1177 const cosU2 = Math.cos(U2); 1178 1179 const L = endLngRad - startLngRad; 1180 1181 // Calculate the longitude difference on the ellipsoid 1182 let lambda = L; 1183 let iterations = 0; 1184 let maxIterations = 20; 1185 while (Math.abs(lambda) > 1e-12 && iterations < maxIterations) { 1186 const sinLambda = Math.sin(lambda); 1187 const cosLambda = Math.cos(lambda); 1188 const t1 = sinU1 * cosLambda; 1189 const t2 = sinU2 * sinLambda; 1190 const t3 = cosU1 * cosU2 * cosLambda - sinU1 * sinU2; 1191 const N = a / Math.sqrt(1 - f * sinU2 * sinU2); 1192 lambda -= (t1 - t2) / (N * Math.sin(N * (t3 + 1e-6))); 1193 iterations++; 1194 } 1195 1196 // Calculate the bearing in radians 1197 const A = Math.cos(U2) * Math.sin(lambda); 1198 const B = Math.cos(U1) * Math.sin(U2) - Math.sin(U1) * Math.cos(U2) * Math.cos(lambda); 1199 const bearingRad = Math.atan2(A, B); 1200 1201 // Convert bearing to degrees and adjust for north being 0° 1202 const bearingDeg = (giga.rad2deg(bearingRad) + 360) % 360; 1203 1204 return bearingDeg; 1205 }, 1206 1207 getDistanceAndBearing: function(startLat, startLong, endLat, endLong) { 1208 return { 1209 distance: giga.getDistance(startLat, startLong, endLat, endLong), 1210 bearing: giga.getBearing(startLat, startLong, endLat, endLong) 1211 }; 1212 }, 1213 1214 getAllBearings: function(lat, lon) { 1215 const oName = `bearings:${lat}:${lon}`; 1216 if (0 && storageAvailable('sessionStorage')) { 1217 if (!!sessionStorage.getItem(oName)) { 1218 return JSON.parse(sessionStorage.getItem(oName)); 1219 } 1220 } 1221 const ret = []; 1222 hotspots.points.forEach( (p, n) => { 1223 if (hasValue(p.lat, p.lon)) { 1224 const dist = giga.getDistance(lat, lon, p.lat, p.lon); 1225 if (dist < 300) 1226 ret.push({ 1227 id: p.id, 1228 kind: p.kind, 1229 elevation: p.elevation, 1230 bearing: giga.getBearing(lat, lon, p.lat, p.lon), 1231 distance: dist, 1232 q: p.quality 1233 }); 1234 } 1235 }); 1236 /* 1237 if (storageAvailable('sessionStorage')) { 1238 sessionStorage.setItem(oName, JSON.stringify(ret)); 1239 } 1240 */ 1241 return ret; 1242 }, 1243 1244 getElevationAngle: function(startLat,startLong,startElev, endLat,endLong,endElev) { 1245 // https://tchester.org/sgm/analysis/peaks/how_to_get_view_params.html 1246 const earthRadius = 6371009; 1247 const dist = 1000*giga.getDistance(startLat,startLong,endLat,endLong); 1248 const lambda = giga.rad2deg( Math.asin((endElev-startElev)/dist) - dist/(2*earthRadius) ); 1249 return lambda; 1250 }, 1251 1252 getObjectsFromBounds: function(...args) { 1253 let lat1, lon1, lat2, lon2; 1254 let returnAll = false; 1255 if (args.length === 2 && Array.isArray(args[0]) && Array.isArray(args[1])) { 1256 [lat1, lon1, lat2, lon2] = [...args[0], ...args[1]]; 1257 } else if (args.length === 4) { 1258 [lat1, lon1, lat2, lon2] = [...args]; 1259 } else if (args.length === 1 && args[0] instanceof L.LatLngBounds) { 1260 lat1 = args[0].getSouth(); 1261 lat2 = args[0].getNorth(); 1262 lon1 = args[0].getWest(); 1263 lon2 = args[0].getEast(); 1264 } else if (args.length === 0) { 1265 returnAll = true; 1266 } 1267 if (lat2 < lat1) [lat1, lat2] = [lat2, lat1]; 1268 if (lon2 < lon1) [lon1, lon2] = [lon2, lon1]; 1269 const ret = [];
1270 hotspots.points.forEach( (p, n) => { 1271 if (hasValue(p.lat, p.lon)) { 1272 if (returnAll || (p.lat>=lat1 && p.lat<=lat2 && p.lon>=lon1 && p.lon<=lon2)) { 1273 ret.push(p); 1274 } 1275 } 1276 }); 1277 return ret; 1278 }, 1279 1280 getObjectsFromBearing: function(lat,lon,azimuth,count,mindist,maxdist) { 1281 var bearings = giga.getAllBearings(lat,lon); 1282 mindist = isNumeric(mindist) ? mindist : 0.001; 1283 maxdist = isNumeric(maxdist) ? maxdist : 250.0; 1284 count = count || 20; 1285 bearings = bearings.filter( e => e.distance>=+mindist && e.distance<=+maxdist ); 1286 bearings.forEach( function(e) { 1287 e.originalDiff = e.diff = (e.bearing-azimuth); 1288 e.diff = giga.plusMinus180(e.diff); 1289 }); 1290 return bearings.sort(function(a,b) { 1291 return Math.abs(a.diff)-Math.abs(b.diff); 1292 }).slice(0,count); 1293 }, 1294 1295 getEndPointHaversine: function(startLat,startLong,bearing,distance) { 1296 // Ï is latitude, λ is longitude, θ is the bearing (clockwise from north), δ is the angular distance d/R; d being the distance travelled, R the earthâs radius 1297 startLat = giga.deg2rad(startLat); 1298 startLong = giga.deg2rad(startLong); 1299 bearing = giga.deg2rad(bearing); 1300 const dr = distance/6371009; 1301 const endLat = Math.asin( Math.sin(startLat)*Math.cos(dr) + Math.cos(startLat)*Math.sin(dr)*Math.cos(bearing) ); 1302 const endLong = startLong + Math.atan2(Math.sin(bearing)*Math.sin(dr)*Math.cos(startLat), Math.cos(dr)-Math.sin(startLat)*Math.sin(endLat)); 1303 1304 // The longitude can be normalised to â180â¦+180 using (lon+540)%360-180 1305 return { lat: giga.rad2deg(endLat), lon: (giga.rad2deg(endLong)+540)%360-180 }; 1306 }, 1307 1308 getEndPoint: function(startLat, startLon, bearing, distance) { 1309 return giga.lineOfSight.vincentyDirect(startLat, startLon, giga.deg2rad(bearing), distance); 1310 }, 1311 1312 getEndPoint2: function(startLat,startLong,bearing,distance) { 1313 // Vincenty's algorithm 1314 // https://gist.github.com/mathiasbynens/354587/4137c2350d7cee9b757444ef3a6f13a1f69c2abc 1315 const a = 6378137, 1316 b = 6356752.3142, 1317 f = 1 / 298.257223563, // WGS-84 ellipsoid 1318 s = distance, 1319 alpha1 = giga.deg2rad(bearing), 1320 sinAlpha1 = Math.sin(alpha1), 1321 cosAlpha1 = Math.cos(alpha1), 1322 tanU1 = (1 - f) * Math.tan(giga.deg2rad(startLat)), 1323 cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1, 1324 sigma1 = Math.atan2(tanU1, cosAlpha1), 1325 sinAlpha = cosU1 * sinAlpha1, 1326 cosSqAlpha = 1 - sinAlpha * sinAlpha, 1327 uSq = cosSqAlpha * (a * a - b * b) / (b * b), 1328 A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))), 1329 B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 1330 var sigma = s / (b * A), 1331 sigmaP = 2 * Math.PI; 1332 while (Math.abs(sigma - sigmaP) > 1e-12) { 1333 var cos2SigmaM = Math.cos(2*sigma1 + sigma), 1334 sinSigma = Math.sin(sigma), 1335 cosSigma = Math.cos(sigma), 1336 deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 1337 sigmaP = sigma; 1338 sigma = s / (b * A) + deltaSigma; 1339 }; 1340 var tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1, 1341 lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, (1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp)), 1342 lambda = Math.atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1), 1343 C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)), 1344 L = lambda - (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM))), 1345 revAz = Math.atan2(sinAlpha, -tmp); // final bearing 1346 return { lat: giga.rad2deg(lat2), lon: +startLong + giga.rad2deg(L) }; 1347 }, 1348 1349 getBearingErrors: function(pan) { 1350 pan = pan || giga.currentPanorama.getID(); 1351 const info = hotspots.panoramas[pan]; 1352 const diffArr = []; 1353 1354 for (const e of hotspots.data[pan]) { 1355 const w = e.getAttribute('wiki'); 1356 if (hasValue(w)) { 1357 const p = hotspots.points.get(w); 1358 if (hasValue(p?.lat, p?.lon)) { 1359 const bearingOnPan = giga.hposToAzimuth(+e.getAttribute('ath')); 1360 const bearingReal = giga.getBearing(info.lat, info.lon, p.lat, p.lon); 1361 const diff = bearingReal - bearingOnPan; 1362 diffArr.push({wiki: w, diff: diff, bearingReal: bearingReal, bearingOnPan: bearingOnPan, ath: +e.getAttribute('ath')}); 1363 } 1364 } 1365 } 1366 1367 diffArr.sort( (a, b) => a.ath-b.ath ); 1368 return diffArr; 1369 }, 1370 1371 minimizeErrors: function(pan) { 1372 pan = pan || giga.currentPanorama.getID(); 1373 const allErr = giga.getBearingErrors(pan); 1374 const mapped = allErr.map( function(x) { return [x.ath,x.diff]; } ); 1375 const err = mapped.map( function(x) { return x[1]; } ); 1376 const result = regression.linear(mapped); 1377 const gradient = result.equation[0]; 1378 const yIntercept = result.equation[1]; 1379 console.log('result:',result.string, '| count: '+err.length, '| min: '+Math.min(...err), '| max: '+Math.max(...err), '| mean: '+mean(err)); 1380 }, 1381 1382 KML: { 1383 mimeType: 'application/vnd.google-earth.kml+xml', 1384 makeDocument: function(name, elements) { 1385 return '<'+'?xml version="1.0" encoding="UTF-8"'+'?>'+'<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"> ' + 1386 '\n<Document>\n<name>'+name+'</name>\n' + giga.KML.makeDefaultStyle() + '\n' + elements.join('\n') + '\n</Document></kml>'; 1387 }, 1388 makePoint: function(o) { 1389 return '<Placemark> <name>' + escapeXML(o.name) + '</name> <styleUrl>#def</styleUrl> <LookAt> <longitude>' + o.lon + '</longitude> <latitude>' + o.lat + '</latitude> <altitude>0</altitude> <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode> </LookAt> ' + 1390 '<Point><coordinates>' + o.lon + ',' + o.lat + ',0</coordinates></Point> </Placemark>'; 1391 }, 1392 makeDefaultStyle: function() { 1393 return `<Style id="hl">\n\t<IconStyle><color>ff0000ff</color><scale>0.72</scale><Icon><href>https://maps.google.com/mapfiles/kml/shapes/placemark_circle_highlight.png</href></Icon></IconStyle> 1394 \t<ListStyle></ListStyle>\n\t<LabelStyle><scale>0.7</scale></LabelStyle>\n</Style>\n<Style id="df">\n\t<IconStyle><color>ff3300ff</color><scale>0.6</scale> 1395 <Icon><href>https://maps.google.com/mapfiles/kml/shapes/placemark_circle.png</href></Icon></IconStyle>\n\t<ListStyle></ListStyle>\n 1396 \t<LabelStyle><scale>0.6</scale></LabelStyle>\n</Style>\n<StyleMap id="def">\n\t<Pair><key>normal</key><styleUrl>#df</styleUrl></Pair><Pair><key>highlight</key><styleUrl>#hl</styleUrl></Pair>\n</StyleMap>\n`; 1397 }, 1398 createPoint: function(o) { 1399 // name,lat,lon 1400 return giga.KML.makeDocument(o.name,[giga.KML.makePoint(o)]); 1401 }, 1402 createPoints: function(name,points) { 1403 // name,lat,lon 1404 return giga.KML.makeDocument(name,points.map( o => giga.KML.makePoint(o) )); 1405 }, 1406 createLine: function(o) { 1407 // name,description,startLat,startLong,endLat,endLong,bearing,distance 1408 const range = o.distance || giga.getDistance(o.startLat,o.startLong,o.endLat,o.endLong); 1409 const heading = o.bearing || giga.getBearing(o.startLat,o.startLong,o.endLat,o.endLong); 1410 return '<'+'?xml version="1.0" encoding="UTF-8"'+'?>'+'<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"> <Document> <name>'+o.name+'</name> <Style id="inline"> <LineStyle> <color>ff0000ff</color> <width>2</width> </LineStyle> </Style> <StyleMap id="inline0"> <Pair> <key>normal</key> <styleUrl>#inline1</styleUrl> </Pair> <Pair> <key>highlight</key> <styleUrl>#inline</styleUrl> </Pair> </StyleMap> <Style id="inline1"> <LineStyle> <color>ff0000ff</color> <width>2</width> </LineStyle> </Style> <LookAt> <longitude>'+o.endLong+'</longitude> <latitude>'+o.endLat+'</latitude> <altitude>0</altitude> <range>'+range+'</range> <tilt>88.0</tilt> <heading>'+heading+'</heading> <altitudeMode>relativeToGround</altitudeMode> </LookAt> <Placemark> <name>'+o.name+'</name> <styleUrl>#inline0</styleUrl> <LineString> <altitudeMode>
1410clampToGround</altitudeMode> <tessellate>1</tessellate> <coordinates>'+o.startLong+','+o.startLat+',0 '+o.endLong+','+o.endLat+',0 </coordinates> </LineString> </Placemark> </Document> </kml>'; 1411 }, 1412 createBearingLine: function(name,startLat,startLong,bearing,distance) { 1413 const endPoint = giga.getEndPoint(startLat,startLong,bearing,distance); 1414 return giga.KML.createLine({ 1415 name: name, 1416 startLat: startLat, 1417 startLong: startLong, 1418 endLat: endPoint.lat, 1419 endLong: endPoint.lon, 1420 bearing: bearing, 1421 distance: distance 1422 }); 1423 }, 1424 saveAs: function(contents,filename) { 1425 const blob = new Blob([contents], {type: giga.KML.mimeType+";charset=utf-8"}); 1426 saveAs(blob, filename); 1427 } 1428 }, 1429 1430 configCookies: { 1431 get: function(el) { 1432 return Cookies.get(el); 1433 }, 1434 set: function(el, val) { 1435 Cookies.set(el,val,{expires:365,'SameSite':'Lax'}) 1436 } 1437 }, 1438 1439 config: { 1440 get: function(el) { 1441 return localStorage.getItem(el); 1442 }, 1443 set: function(el, val) { 1444 localStorage.setItem(el,val); 1445 return giga.config; 1446 } 1447 }, 1448 1449 addUnderscores: function(str) { 1450 return str.trim().replace(/ /g, '_'); 1451 }, 1452 1453 escapeUnicode: function(str) { 1454 // stackoverflow.com/questions/21014476/javascript-convert-unicode-string-to-javascript-escape 1455 return str.replace(/[^\0-~]/g, function(ch) { 1456 return "\\u" + ("0000" + ch.charCodeAt().toString(16)).slice(-4); 1457 }); 1458 }, 1459 1460 makePanoramaLink: function(text,p) { 1461 return `<a title="Przejdź do panoramy!" data-panorama="${p}" class="panorama-hotspot-link" href="view,${p}.html">${text}</a>`; 1462 }, 1463 1464 makeHotspotLink: function(text,point,p,h,v,z,currentBearing) { 1465 const ppan = hotspots.panoramas[p]; 1466 const bearing = giga.hposToAzimuthGeneric(+h, +ppan.azimuthleft, +ppan.azimuthright, 1); 1467 let bearingDiffText = ''; 1468 let diff = 999; 1469 let dist; 1470 1471 if (hasValue(currentBearing)) { 1472 diff = Math.abs(currentBearing-bearing); 1473 if (diff>180) diff = 360-diff; 1474 bearingDiffText = `, różnica: ${diff.toFixed(2)}°`; 1475 } 1476 if (hasValue(point?.lat,point?.lon)) { 1477 dist = giga.getDistance(+ppan.lat, +ppan.lon, +point.lat, +point.lon); 1478 } 1479 return `<a title="Pokaż na panoramie! (${dist ? `odlegÅoÅÄ: ${dist.toFixed(2)} km; ` : ''}azymut ${bearing.toFixed(2)}°${bearingDiffText})" data-bearing="${bearing.toFixed(2)}" 1480 data-bearingdiff="${diff.toFixed(2)}" data-distance="${dist ? dist.toFixed(2) : ''}" data-panorama="${p}" data-ath="${h}" data-atv="${v}" data-zoom="${z}" 1481 class="panorama-hotspot-link" href="view,${p}.html#!${h},${v},${z}">${text}</a> 1482 <a title="Pokaż podglÄ d w okienku" data-panorama="${p}" data-ath="${h}" data-atv="${v}" data-zoom="${z}" class="panorama-hotspot-link-mini" href="view,${p}.html#!${h},${v},${z}">▭</a>`; 1483 1484 }, 1485 1486 makeHotspotLinkOnly: function(text,p,h,v,z) { 1487 return '<a title="Pokaż na panoramie!" data-panorama="'+p+'" data-ath="'+h+'" data-atv="'+v+'" data-zoom="'+z+'" href="view,'+p+'.html#!'+h+','+v+','+z+'">'+text+'</a>'; 1488 }, 1489 1490 lastPushStateObject: null, 1491 lastPopStateObject: null, 1492 1493 stateObjectsEqual: function(o1,o2) { 1494 //console.log('stateObjectsEqual',o1,o2); 1495 for (const prop in o1) if (o1?.[prop]!=o2?.[prop]) return false; 1496 return true; 1497 }, 1498 1499 savePositionTimeout: null, 1500 1501 savePosition: function(panid, hlookat, vlookat, vfov) { 1502 //console.log('savePosition!'); 1503 //document.location.hash = panid + ',' + hlookat + ',' + vlookat + ',' + vfov; 1504 if (window.onpopstate===null) { 1505 window.onpopstate = function(ev) { 1506 window.setTimeout( () => { 1507 var st = ev.state; 1508 console.log(st); 1509 if (giga.stateObjectsEqual(st,giga.lastPopStateObject)) { 1510 console.log('pop state object equal, going back once more'); 1511 history.back(); 1512 } else if (st!==null) { 1513 giga.lastPopStateObject = st; 1514 if (st.panid!=giga.currentPanorama.loadedID) { 1515 giga.loadPanorama(st.panid, st.hlookat, st.vlookat, st.vfov); 1516 } else { 1517 window.setTimeout( function() { 1518 k.call('lookto('+st.hlookat+', '+st.vlookat+', '+st.vfov+',smooth(500,-400,720));'); 1519 }, 40); 1520 } 1521 } 1522 }, 0); 1523 } 1524 } 1525 const newURL = `view,${panid}.html#!${hlookat},${vlookat},${vfov}`; 1526 var stateObject = { panid, hlookat, vlookat, vfov }; 1527 //console.log(hlookat, vlookat, vfov, ' ', newURL, ' :: ', panid, ':', prevPanID ); 1528 if (!giga.stateObjectsEqual(stateObject,giga.lastPushStateObject)) { 1529 giga.lastPushStateObject = stateObject; 1530 //console.log('pushing... ', newURL); 1531 history.pushState( stateObject, '', newURL); 1532 } 1533 giga.prevPanID = panid; 1534 }, 1535 1536 updateViewInfo: function() { 1537 const currentView = giga.getCurrentViewParameters(); 1538 if (giga.compass.container) giga.compass.update(currentView.range,currentView.center); 1539 document.getElementById('view-range').innerHTML = currentView.range.toFixed(1)+'°'; 1540 document.getElementById('view-level').innerHTML = currentView.level>=0 ? currentView.level : ' '; 1541 giga.currentPanorama.overlayMap.updateView(currentView); 1542 giga.peakFinder.updateView(); 1543 }, 1544 1545 showInfoboxTab: function() { 1546 document.querySelector('#panel-navi .panel-tab-button[data-title="obiekty"]')?.click(); 1547 }, 1548 1549 makePointLatLonLinks: function(point) { 1550 var ret = ''; 1551 ret += ' <a class="" href="https://www.openstreetmap.org/?mlat='+point.lat+'&mlon='+point.lon+'&zoom=15" target="_blank"><abbr title="OpenStreetMap">OSM</abbr></a> '+ 1552 ' <a class="" href="https://www.opentopomap.org/?mlat='+point.lat+'&mlon='+point.lon+'&zoom=15#map=15/'+point.lat+'/'+point.lon+'" target="_blank"><abbr title="OpenTopoMap">OTM</abbr></a> '+ 1553 ' <a class="" href="https://www.geonames.org/maps/google_'+point.lat+'_'+point.lon+'.html" target="_blank"><abbr title="GeoNames">GEON</abbr></a> '+ 1554 ' <a class="" href="https://wikimapia.org/#lang=pl&lat='+point.lat+'&lon='+point.lon+'&z=14&m=w" target="_blank"><abbr title="Wikimapia">WIKM</abbr></a> '+ 1555 ' <a class="" href="https://www.google.com/maps?ll='+point.lat+','+point.lon+'&z=14" target="_blank"><abbr title="Google Maps">GM</abbr></a> '+ 1556 ' <a class="" href="https://earth.google.com/web/search/'+point.lat+','+point.lon+'/" target="_blank"><abbr title="Google Earth for the Web">GEW</abbr></a> '; 1557 1558 if (point.lat>=49.0 && point.lat<=51.2 && point.lon>=14.07 && point.lon<=24.1) { 1559 // mapa turystyczna 1560 ret += ' <a class="" href="https://mapa-turystyczna.pl/#'+point.lat+'/'+point.lon+'/15" target="_blank"><abbr title="Mapa Turystyczna">MT</abbr></a> '; 1561 1562 // geoportal 1563 proj4.defs("EPSG:2180","+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); 1564 const pl1992 = proj4("EPSG:2180").forward([+point.lon,+point.lat]); 1565 const xoffset = 500, yoffset = 350, bbox = (pl1992[0]-xoffset) +','+(pl1992[1]-yoffset) +','+(pl1992[0]+xoffset) +','+(pl1992[1]+yoffset); 1566 ret += ' <a class="" href="https://mapy.geoportal.gov.pl/imap/Imgp_2.html?bbox=' + bbox + '" target="_blank"><abbr title="geoportal">GEO</abbr></a> '; 1567 ret += ' <a class="" href="https://mapy.geoportal.gov.pl/mobile/?bbox=' + bbox + '" target="_blank"><abbr title="geoportal mobile">GEO/M</abbr></a> '; 1568 ret += ` <a class="" href="https://mapy.geoportal.gov.pl/imapnext/imap/index.html?moduleId=modulTur&mapview=${point.lat}%2C${point.lon}%2C10000s" target="_blank"><abbr title="geoportal imapNext">GEO/N</abbr></a> `; 1569 1570 // geoserwis GDOÅ 1571 ret += ' <a class="" href="https://geoserwis.gdos.gov.pl/mapy/?extent=' + bbox + '&addLayers=,GDOS-misc:mezoregiony&mapNr=2&styleMask=013013013013013013013013013013" target="_blank"><abbr title="geoserwis GDOÅ">GDOÅ</abbr></a> '; 1572 1573 // regionalizacja 2018 1574 ret += ' <a class="" href="https://mapa.wirtualneszlaki.pl/krainy-geograficzne-polska#14/'+point.lat+'/'+point.lon+'/OSM_bw-mezoregiony" target="_blank"><abbr title="Krainy geograficzne Polski">REG</abbr></a> '; 1575 } 1576 1577 if (point.country?.includes('PL') && point.range?.startsWith('Tatry')) { 1578 //console.log(point); 1579 const pl1992 = proj4("EPSG:3857").forward([+point.lon,+point.lat]); 1580 proj4.defs("EPSG:9700","+proj=geocent +ellps=GRS80 +units=m +no_defs +type=crs"); 1581 const pl9700 = proj4("EPSG:9700").forward([+point.lon,+point.lat]); 1582 /* map types: 1583 1 = model 3D, 17 = nmt, 18 = nmpt, 19 = QL HS RGB, 20 = QL HS CIR, 21 = CHM, 22 = zdjÄcia ukoÅne, 23 = OpenStreetMap, 1584 ortofotomapy: 1585 2: 2020 - RGB, 3: 2020 - CIR, 4: 2018 - RGB, 5: 2017 - RGB, 6: 2017 - CIR, 1586 7: 2015 - RGB, 8: 2015 - CIR, 9: 2013 - RGB, 10: 2013 - CIR, 11: 2012 - RGB,
1587 12: 2012 - CIR, 13: 2009 - RGB, 14: 1994 - RGB, 15: 1977 - PAN, 16: 1955 - PAN 1588 */ 1589 const mapType = 2; 1590 const tpnLink = `https://tpn.obliview.com/?d=0&l=-1&r=${mapType}&z=17&x=${pl1992[0]}&y=${pl1992[1]}&poz=${pl9700[0]},${pl9700[1]},4816182.96,-0.6129,-0.2233,-0.7580,-0.7122,-0.2595,0.6523,-0.3424,0.9396,-0.0000&k=orto&akLay=empty`; 1591 ret += ` <a class="" href="${tpnLink}" target="_blank"><abbr title="Geoportal TATRY">TPN</abbr></a> `; 1592 } 1593 1594 if (point.lat>=47.645846 && point.lat<=49.714710 && point.lon>=16.661685 && point.lon<=22.620043 || point.country.includes('SK')) { 1595 /* 1596 ret += ` <a class="" href="https://mapy.hiking.sk/?y=${point.lat}&x=${point.lon}" target="_blank"><abbr title="mapy.hiking.sk">H</abbr></a> 1597 <a class="" href="https://zbgis.skgeodesy.sk/mapka/zakladna-mapa?bm=zbgis&z=14&c=${point.lon},${point.lat}#!" target="_blank"><abbr title="zbgis.skgeodesy.sk">SK</abbr></a> 1598 <a class="" href="https://zbgis.skgeodesy.sk/mapka/sk/teren/toc?bm=orto&z=19&c=${point.lon},${point.lat}&sc=n&pos=${point.lat},${point.lon},18" target="_blank"><abbr title="zbgis.skgeodesy.sk LIDAR DEM">SKL</abbr></a> 1599 <a class="" href="https://zbgis.skgeodesy.sk/mapka/sk/teren/export?pos=${point.lat}%2C${point.lon}%2C16" target="_blank"><abbr title="zbgis.skgeodesy.sk DMR export">ZEX</abbr></a> 1600 <a class="" href="https://www.staremapy.sk/?zoom=14&lat=${point.lat}&lng=${point.lon}&map=sazp_retm25" target="_blank"><abbr title="StareMapy.sk">SM</abbr></a> 1601 <a class="" href="https://www.cykloserver.cz/cykloatlas/#pos=${point.lat}P${point.lon}P15@b=2" target="_blank"><abbr title="cykloserver.cz">CS</abbr></a> 1602 `; 1603 */ 1604 proj4.defs("EPSG:5514","+proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=485,169.5,483.8,7.786,4.398,4.103,0 +units=m +no_defs +type=crs"); 1605 //proj4.defs("EPSG:5514","+proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=589,76,480,0,0,0,0 +units=m +no_defs +type=crs"); 1606 const jtsk = proj4("EPSG:5514").forward([+point.lon,+point.lat]); 1607 1608 ret += ` <a class="" href="https://mapy.hiking.sk/?y=${point.lat}&x=${point.lon}" target="_blank"><abbr title="mapy.hiking.sk">H</abbr></a> 1609 <a class="" href="https://zbgis.skgeodesy.sk/mapka/sk/zakladna-mapa/toc?bm=zbgis&z=14&c=${point.lon},${point.lat}&pos=${point.lat},${point.lon},18" target="_blank"><abbr title="zbgis.skgeodesy.sk">SK</abbr></a> 1610 <a class="" href="https://zbgis.skgeodesy.sk/mapka/sk/teren/toc?bm=orto&z=19&c=${point.lon},${point.lat}&sc=n&pos=${point.lat},${point.lon},18" target="_blank"><abbr title="zbgis.skgeodesy.sk LIDAR DEM">SKL</abbr></a> 1611 <a class="" href="https://zbgis.skgeodesy.sk/mapka/sk/teren/export?pos=${point.lat}%2C${point.lon}%2C16" target="_blank"><abbr title="zbgis.skgeodesy.sk DMR export">ZEX</abbr></a> 1612 <a class="" href="https://geoportal.gov.sk/maps/historical-maps/datasets?view=${Math.round(jtsk[0])},${Math.round(jtsk[1])},1.0&loc=${Math.round(jtsk[0])},${Math.round(jtsk[1])},100" target="_blank"><abbr title="geoportal.gov.sk">GSK</abbr></a> 1613 <a class="" href="https://www.staremapy.sk/?zoom=14&lat=${point.lat}&lng=${point.lon}&map=sazp_retm25" target="_blank"><abbr title="StareMapy.sk">SM</abbr></a> 1614 `; 1615 1616 // https://zbgis.skgeodesy.sk/mapka/sk/teren/toc?pos=49.105934,18.916214,17 1617 } 1618 1619 ret += ` <a class="" href="https://mapy.com/fnc/v1/showmap?mapset=outdoor¢er=${point.lon},${point.lat}&zoom=15&marker=true" target="_blank"><abbr title="mapy.com">MC</abbr></a> `; 1620 if (point.elevation) ret += ' <a class="" href="https://mapy.com/en/zakladni?'+makeParams({ 1621 m3d: 1, 1622 x: point.lon, 1623 y: point.lat, 1624 z: 16, 1625 height: point.elevation, 1626 pitch: -15, 1627 yaw: 0, 1628 base: 'ophoto' 1629 })+'" target="_blank"><abbr title="mapy.com 3D">MC3D</abbr></a> '; 1630 1631 ret += ' <a class="" href="https://gpxmapy.cz/index.html?' + makeParams({ 1632 x1: point.lon, 1633 y1: point.lat, 1634 n1: point.title 1635 }) + '" target="_blank"><abbr title="gpxmapy.cz">GMC</abbr></a> '; 1636 1637 ret += ' <a class="" href="https://web.locusmap.app/pl/?lat='+point.lat+'&lng='+point.lon+'&z=15" target="_blank"><abbr title="Locus Map">LO</abbr></a> '; 1638 ret += ` <a class="" href="https://www.cykloserver.cz/cykloatlas/#pos=${point.lat}P${point.lon}P15@b=2" target="_blank"><abbr title="cykloserver.cz">CS</abbr></a> `; 1639 1640 // ÄÃZK GeoprohlÞeÄ 1641 if (point.lat>=48.58 && point.lat<=51.06 && point.lon>
1641=12.09 && point.lon<=18.86 || point.country.includes('CZ')) { 1642 proj4.defs("EPSG:5514","+proj=krovak +lat_0=49.5 +lon_0=24.83333333333333 +alpha=30.28813972222222 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=570.8,85.7,462.8,4.998,1.587,5.261,3.56 +units=m +no_defs"); 1643 const krovak = proj4("EPSG:5514").forward([+point.lon,+point.lat]), xy = krovak[0]+','+krovak[1]; 1644 ret += ' <a class="" href="https://ags.cuzk.cz/geoprohlizec/?'+makeParams({ xy: xy })+'" target="_blank"><abbr title="ÄÃZK GeoprohlÞeÄ">ÄÃZK</abbr></a> '; 1645 ret += ' <a class="" href="https://ags.cuzk.cz/geoprohlizec/mapa.html?'+makeParams({ xy: xy, name: encodeURIComponent(point.title), b: 'zm', p: '' })+'" target="_blank"><abbr title="ÄÃZK GeoprohlÞeÄ / mapa">Ä/M</abbr></a> '; 1646 ret += ' <a class="" href="https://ags.cuzk.cz/geoprohlizec/mapa.html?'+makeParams({ xy: xy, name: encodeURIComponent(point.title), b: 'orto', p: '' })+'" target="_blank"><abbr title="ÄÃZK GeoprohlÞeÄ / orto">Ä/O</abbr></a> '; 1647 } 1648 1649 if (point.lat>=45.82 && point.lat<=47.81 && point.lon>=5.96 && point.lon<=10.49 || point.country.includes('CH')) { 1650 proj4.defs("EPSG:2056","+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs"); 1651 const lv = proj4("EPSG:2056").forward([+point.lon,+point.lat]); 1652 ret += ' <a class="" href="https://map.geo.admin.ch/?lang=en&topic=ech&bgLayer=ch.swisstopo.pixelkarte-farbe&layers=ch.swisstopo.zeitreihen,ch.bfs.gebaeude_wohnungs_register,ch.bav.haltestellen-oev,ch.swisstopo.swisstlm3d-wanderwege&layers_visibility=false,false,false,false&layers_timestamp=18641231,,,&E='+(lv[0])+'&N='+(lv[1])+'&zoom=8&crosshair=marker" target="_blank"><abbr title="geo.admin.ch">CH</abbr></a> ' 1653 } 1654 1655 if (point.country.includes('DE')) { 1656 ret += ' <a class="" href="https://geoviewer.bgr.de/mapapps4/resources/apps/geoviewer/index.html?lang=en¢er='+point.lon+','+point.lat+',4326&lod=14&basemap=+:wmts_topplus_web_open_141,-:worldtopo_142" target="_blank"><abbr title="BGR Geoportal">BGR</abbr></a> '; 1657 } 1658 1659 if (point.kind=='peak') { 1660 ret += ' <a class="" href="https://peakbagger.com/search.aspx?ss=&tid=R&pid=0&lat='+point.lat+'&lon='+point.lon+'&u=m" target="_blank"><abbr title="Peakbagger">PB</abbr></a> '+ 1661 ' <a class="" href="https://www.peakfinder.com/?'+makeParams({ lat: +point.lat, lng: +point.lon, name: point.id, ele: point.elevation ?? '' })+'" target="_blank"><abbr title="Peakfinder">PF</abbr></a> ' + 1662 ' <a class="" href="https://peakery.com/map/#lat='+point.lat+'&lng='+point.lon+'&zoom=5&elev_min=0&elev_max=29500&prom_min=0&prom_max=29500&length_min=0&length_max=20&vertical_min=0&vertical_max=10000" target="_blank"><abbr title="Peakery">PR</abbr></a> ' + 1663 ''; 1664 1665 } 1666 if (point.kind=='peak' || point.kind=='saddle') { 1667 ret += ' <a class="" href="'+giga.overpass.buildQuery(point)+'" target="_blank"><abbr title="Overpass query">OP</abbr></a> ' + 1668 ' <a class="" href="'+giga.overpass.buildQuery(point,true)+'" target="_blank"><abbr title="Overpass query around">OP/a</abbr></a> '; 1669 } 1670 return ret; 1671 }, 1672 1673 hoverMapTimeout: null, 1674 1675 hoverOut: function() { 1676 //giga.currentPanorama.overlayMap.removePoint(); 1677 if (giga.hoverMapTimeout) { 1678 window.clearTimeout(giga.hoverMapTimeout); 1679 giga.hoverMapTimeout = null; 1680 } 1681 }, 1682 1683 showHover: function(hsname) { 1684 //console.log('showHover:'+hsname); 1685 const alreadyDisplayed = $('#infobox').data('hs')==hsname; 1686 1687 $('#infobox').data('hs',hsname); 1688 giga.showInfoboxTab(); 1689 1690 const h = k.hotspot.getItem(hsname); 1691 const info = k.info; 1692 var point = {}; 1693 var elev = rangeInfo = otherNamesText = ''; 1694 var title = h?.title; 1695 1696 if (hasValue(h.id) && hasValue(hotspots.points.get(h.id))) { 1697 point = hotspots.points.get(h.id); 1698 } 1699 const hasCoords = hasValue(point.lat,point.lon); 1700 if (hasCoords && giga.mapycom.isShown()) { 1701 //giga.currentPanorama.overlayMap.showPoint(point.lat,point.lon,point?.title); 1702 if (giga.hoverMapTimeout) window.clearTimeout(giga.hoverMapTimeout); 1703 giga.hoverMapTimeout = window.setTimeout ( () => { 1704 giga.mapycom.flyToWithLine(point.lat,point.lon,+info.lat,+info.lon); 1705 }, 100); 1706 } 1707 if (alreadyDisplayed) return; 1708 1709 //console.log(point); 1710 if (hasValue(point.title)) title = point.title; 1711 title = `<span data-name="${hsname}" class="hs">${title}</span>`;
1712 if (hasValue(point.elevation)) elev = ` (${point.elevation} m)`; 1713 if (hasValue(point.names)) { 1714 const uniqNames = [...new Set(point.names)]; 1715 if (uniqNames.length>1) { 1716 otherNamesText = ' (' + uniqNames.slice(1).map( s => `<span title="">${s}</span>` ).join(', ') + ') '; 1717 } 1718 } 1719 var infotext = '<b class="hs-header">' + title + otherNamesText + elev + '</b>' + (!hasValue(point.id)?(' <i title="brak obiektu "'+(h.id.replace(/\"/g, '"'))+'" w bazie!" class="fas fa-exclamation-triangle warning"></i>'):''); 1720 var distances = []; 1721 const altDistH = hasValue(point.lat,point.lon) ? giga.getDistance(info.lat,info.lon,point.lat,point.lon) : ''; 1722 if (hasValue(altDistH)) distances.push(altDistH.toFixed(1)); 1723 if (distances.length>0) { 1724 infotext = `<abbr title="OdlegÅoÅÄ do obiektu: ${altDistH.toFixed(3)} km" class="distance">${distances.join('/')} km</abbr> ${infotext}`; 1725 } 1726 //console.log(point); 1727 if (point.q=='raw') infotext += ' <i class="fab fa-dev" title="Punkt niezweryfikowany!"></i>'; 1728 $('#infobox').html(infotext+'<div id="wiki"></div><div id="futo"></div>').fadeIn('fast'); 1729 1730 if (giga.advanced) { 1731 var extraInfo = []; 1732 1733 // wspóÅrzÄdne punktu 1734 if (hasCoords) { 1735 const openCode = OpenLocationCode.encode(point.lat,point.lon); 1736 const coordsDeg = dmsCoords(+point.lat,+point.lon); 1737 const src = giga.isPointVerified(point) ? ` <i title="źródÅo: ${point?.source}" class="far fa-thumbs-up"></i>` : ''; 1738 extraInfo.push(`<div class="ind"><span class="download download-point-kml alt-copy clickable" data-copy="${coordsDeg}" 1739 data-lat="${point.lat}" data-lon="${point.lon}" data-name="${point.wiki}"><i title="wspóÅrzÄdne • ${openCode}" class="fas fa-fw fa-map-marker-alt"></i> 1740 <span title="${coordsDeg}">${point.lat.toFixed(5)}, ${point.lon.toFixed(5)}</span></span> 1741 <span class="open-mapy-cz clickable" data-lat="${point.lat}" data-lon="${point.lon}" 1742 data-name="${point.names[0]}"><i title="Otwórz mapkÄ mapy.com" class="fas fa-map-marked-alt"></i></span>${src}<span id="hover-point-coords"></span></div>`); 1743 extraInfo.push(`<div class="ind"><i title="pokaż w innych serwisach" class="far fa-fw fa-map"></i> ${giga.makePointLatLonLinks(point)}</div>`); 1744 } else { 1745 const dd = `<div class="ind"><i class="fas fa-fw fa-search"></i> 1746 <a class="" href="https://www.zrg/?query=${encodeURIComponent(h.title)}" target="_blank" title="Wyszukaj w serwisie OpenStreetMap">OSM</a> · 1747 <a class="" href="https://www.geonames.org/search.html?q=${encodeURIComponent(h.title)}" target="_blank" title="Wyszukaj w serwisie GeoNames">GeoNames</a> · 1748 <a class="" href="https://mapy.com/?q=${encodeURIComponent(h.title)}" target="_blank" title="Wyszukaj w serwisie mapy.com">Mapy.com</a> 1749 </div>`; 1750 extraInfo.push(dd); 1751 } 1752 if (hasValue(point.height)) { 1753 extraInfo.push(`<div class="ind"><i title="wysokoÅÄ obiektu" class="fas fa-fw fa-long-arrow-alt-up"></i> ${point.height} m</div>`); 1754 } 1755 1756 if (hasValue(point.futo) || (hasValue(point.range) && (point.range=='Tatry Wysokie' || point.range=='Tatry Bielskie'))) { 1757 let aa = $('<a/>').attr('href','#').addClass('futo-search').data('id',point.futo||'').data('name',point.title).html('szukaj w sÅowniku Endre Futó »'); 1758 aa.one('click', function(ev) { 1759 ev.preventDefault(); 1760 giga.futo.replace($(this)); 1761 }); 1762 $('#futo').append(aa); 1763 } 1764 1765 // azymut punktu 1766 var bearingOnPan = giga.hposToAzimuth(+h.ath); 1767 var azimuthText = `<span class="download download-line-kml clickable standard" data-azimuth="${bearingOnPan}">${bearingOnPan.toFixed(2)}°</span>`;
1768 if (hasCoords) { 1769 const bearingReal = giga.getBearing(info.lat,info.lon,point.lat,point.lon); 1770 let bearingDiff = giga.plusMinus180(bearingOnPan - bearingReal); 1771 1772 const vin = giga.vincentyInverse(info.lat,info.lon,point.lat,point.lon); 1773 const bearingRealVincentyInitial = vin.initialBearing; 1774 const bearingRealVincentyFinal = vin.finalBearing; 1775 1776 const bearingRealVincenty = giga.getBearingVincenty(info.lat,info.lon,point.lat,point.lon); 1777 let bearingDiffVincenty = giga.plusMinus180(bearingOnPan - bearingRealVincenty); 1778 let prefix = ''; 1779 let opacity = Math.abs(bearingDiff)<0.03 ? 0.3 : 1; 1780 if (Math.abs(bearingDiff)<0.005) { 1781 prefix = '<i title="różnica miÄdzy azymutem na mapie i na zdjÄciu" class="far fa-circle"></i>'; 1782 } else if (bearingDiff>0) { 1783 prefix = `<i title="różnica miÄdzy azymutem na mapie i na zdjÄciu" class="fas fa-arrow-circle-left" style="opacity:${opacity}"></i>`; 1784 } else { 1785 prefix = `<i title="różnica miÄdzy azymutem na mapie i na zdjÄciu" class="fas fa-arrow-circle-right" style="opacity:${opacity}"></i>`; 1786 } 1787 prefix += ' '; 1788 azimuthText += ` (<i title="azymut wynikajÄ cy z mapy" class="far fa-map"></i> 1789 <span class="download download-line-kml clickable standard" data-azimuth="${bearingReal}"> 1790 <span title="(V: ${bearingRealVincenty.toFixed(2)}...${bearingRealVincentyFinal.toFixed(2)}°)">${bearingReal.toFixed(2)}° (V: ${bearingRealVincenty.toFixed(2)}°)</span>, 1791 <span title="${bearingDiff.toFixed(4)}°">${prefix}${Math.abs(bearingDiff).toFixed(2)}</span>°</span>)`; 1792 //azimuthText += ` (<i title="azymut wynikajÄ cy z mapy" class="far fa-map"></i> <span class="download download-line-kml clickable standard" data-azimuth="${bearingReal}">${bearingReal.toFixed(2)}°, ${prefix}${Math.abs(bearingDiff).toFixed(2)}°</span>)`; 1793 } 1794 extraInfo.push(`<div class="ind"><i title="azymut" class="fas fa-fw fa-compass"></i> ${azimuthText}</div>`); 1795 1796 let profileText; 1797 //console.log(point); 1798 if (hasCoords && point.elevation) { 1799 const elevationAngle = giga.getElevationAngle(info.lat,info.lon,info.elevation, point.lat,point.lon,point.elevation); 1800 extraInfo.push(`<div class="ind"><i title="kÄ t pionowy" class="fas fa-fw fa-arrows-alt-v"></i> ${elevationAngle.toFixed(2)}°</div>`); 1801 } 1802 if (hasCoords) { 1803 profileText = document.createElement('span'); 1804 profileText.classList.add('profile'); 1805 profileText.innerHTML = ' profil »'; 1806 profileText.setAttribute('title', 'oblicz i pokaż profil widzialnoÅci do tego punktu'); 1807 profileText.addEventListener('click', async () => giga.lineOfSight.checkFromCurrent(point)); 1808 const distHaversine = +giga.getDistance(info.lat,info.lon,point.lat,point.lon); 1809 const distVincenty = +giga.getDistanceVincenty(info.lat,info.lon,point.lat,point.lon); 1810 let dist3Dtext = ''; 1811 if (point.elevation && info.elevation) { 1812 const distVincenty3D = +giga.getDistanceVincenty3D(info.lat,info.lon,+info.elevation,point.lat,point.lon,+point.elevation); 1813 dist3Dtext = `, <span title="odlegÅoÅÄ wyliczona metodÄ Vincenty'ego z uwzglÄdnieniem wysokoÅci">V3D: ${distVincenty3D.toFixed(3)} km</span> (${addSign(Math.round(1000*(distVincenty3D-distHaversine)))} m)`; 1814 } 1815 extraInfo.push(`<div class="ind"><i title="odlegÅoÅci" class="fas fa-fw fa-arrows-alt-h"></i> <span title="odlegÅoÅÄ wyliczona wzorem haversine">Hav: ${distHaversine.toFixed(3)} km</span>, 1816 <span title="odlegÅoÅÄ wyliczona metodÄ Vincenty'ego">V: ${distVincenty.toFixed(3)} km</span> (${addSign(Math.round(1000*(distVincenty-distHaversine)))} m)${dist3Dtext}</div>` 1817 ); 1818 } 1819 1820 if (!!point.range) extraInfo.push(`<div class="range ind" title="rejon"><i class="fas fa-fw fa-map-signs"></i> <span class="clickable zoom-to-range">${point.range}</span></div>`); 1821 if (!!point.country && point.country.length>0) extraInfo.push(`<div class="country ind" title="paÅstwo"><i class="far fa-fw fa-flag"></i> ${point.country.map(code => ISOCountryCode(code)).join(', ')}</div>`); 1822 if (!!point.peakbagger || !!point.summitpost || !!point.mapycz || !!point.osm) { 1823 let sitelinks = ''; 1824 if (!!point.peakbagger) sitelinks += `PB <a target="_blank" href="https://peakbagger.com/peak.aspx?pid=${point.peakbagger}">${point.peakbagger}</a> `; 1825 if (!!point.summitpost) sitelinks += `SP <a target="_blank" href="https://www.summitpost.org/${kebabCase(h.title)}/${point.summitpost}">${point.summitpost}</a> `; 1826 if (!!point.osm) { 1827 sitelinks += `OSM <a target="_blank" href="https://www.openstreetmap.org/node/${point.osm}">${point.osm}</a> `; 1828 if (point.country.includes('SK')) sitelinks += ` <a target="_blank" href="https://poi.oma.sk/n${point.osm}">OMA</a> `; 1829 } 1830 if (!!point.mapycz) { 1831 let [msource, mid] = point.mapycz.split(' '); 1832 sitelinks += `MAPY.COM <a title="Strona tego obiektu w serwisie mapy.com" target="_blank" href="https://mapy.com/en/turisticka?source=${msource}&id=${mid}">O</a> 1833 <a title="ZdjÄcia tego obiektu w serwisie mapy.com" target="_blank" href="https://mapy.com/en/turisticka?source=${msource}&id=${mid}&gallery=1">G</a> `; 1834 } 1835 if(sitelinks!='') extraInfo.push(`<div class="websites ind"><i class="fas fa-mountain"></i> ${sitelinks}</div>`); 1836 } 1837 1838 var hlevel = '<span class="level-symbol" title="'+makeAttr(h.id)+'">'+(h.level||0)+'</span>'; 1839 $('#infobox').append('<div class="infobox-section extra-info"><h2>Dodatkowe informacje '+hlevel+'</h2>'+extraInfo.join(' ')+'</div>'); 1840 if (profileText) document.querySelector('#infobox h2')?.appendChild(profileText); 1841 1842 $('#hover-point-coords').html(''); 1843 1844 if (hasCoords) { 1845 if (!!point.country && point?.country.includes('PL')) giga.fetchGugikElevation(point.lat,point.lon).then( t => { 1846 if (!!t && t!=='0' && $('#infobox').data('hs')==hsname) $('#hover-point-coords').append(' <span title="wysokoÅÄ wg NMT GUGiK" data-title="'+makeAttr(point.id)+'" data-lat="'+point.lat+'" data-lon="'+point.lon+'" class="clickable nmt-refine">NMT '+t+' m</span>'); 1847 return t; 1848 }); 1849 if (!!point.country && !(point?.country.length==1 && point?.country[0]=='PL') && (point?.country.includes('SK') || point?.range.startsWith('Tatry'))) { 1850 giga.fetchZbgisElevation(point.lat,point.lon).then( t => { 1851 //if (t && t!=0 && t?.attributes['Pixel Value']!='NoData' && $('#infobox').data('hs')==hsname) {
1852 if (t && t?.attributes['Stretch.Pixel Value']!='NoData') { 1853 let spv = +(+(t?.attributes['Stretch.Pixel Value']).replace(',','.')).toFixed(1); 1854 if ($('#infobox').data('hs')==hsname && !isNaN(spv)) $('#hover-point-coords').append(' <span title="wysokoÅÄ wg NMT ZBGIS ('+t?.layerName+')" data-title="'+makeAttr(point.id)+'" data-lat="'+point.lat+'" data-lon="'+point.lon+'" class="clickable zbgis-refine">ZBGIS '+spv+' m</span>'); 1855 } 1856 return t; 1857 }); 1858 } 1859 if (!!point.country && point.country.includes('CZ')) giga.fetchCuzkElevation(point.lat,point.lon).then( t => { 1860 if (!!t && t!='0' && t?.value>0 && $('#infobox').data('hs')==hsname) { 1861 $('#hover-point-coords').append(' <span title="wysokoÅÄ wg NMT ÄÃZK" data-title="'+makeAttr(point.id)+'" data-lat="'+point.lat+'" data-lon="'+point.lon+'" class="clickable cuzk-refine">ÄÃZK '+ ((+t.value).toFixed(1)) +' m</span>'); 1862 } 1863 return t; 1864 }); 1865 if (!!point.country && point?.country.includes('CH') || point?.range.startsWith('Alpy')) giga.fetchSwissElevation(point.lat,point.lon).then( t => { 1866 if (!!t && t?.height!=0 && $('#infobox').data('hs')==hsname) $('#hover-point-coords').append(' <span title="wysokoÅÄ wg Geoadmin.ch" data-title="'+makeAttr(point.id)+'" data-lat="'+point.lat+'" data-lon="'+point.lon+'" class="clickable swiss-refine">CH '+t.height+' m</span>'); 1867 return t; 1868 }); 1869 } 1870 } 1871 1872 if (hasValue(point.id)) { 1873 const pansFromThere = hotspots.findPanoramasByLocation(point.id); 1874 if (pansFromThere.length>0) { 1875 var pansFromThereTexts = []; 1876 pansFromThere.forEach(function(e) { 1877 pansFromThereTexts.push('<div>'+giga.makePanoramaLink(hotspots.panoramas[e].title, e)+'</div>'); 1878 }); 1879 $('#infobox').append('<div class="infobox-section see-also"><h2>Panoramy z tego punktu ('+pansFromThere.length+')</h2>'+pansFromThereTexts.join(' ')+'</div>'); 1880 } 1881 } 1882 1883 if (hasValue(point.wiki)) { 1884 $('#wiki').html('<img src="img/loader30dark.svg" width="160" height="20">'); 1885 giga.loadWiki('#wiki',point.wiki,point.wikilang); 1886 } else { 1887 const wikiPrompt = giga.advanced ? ' <a class="standard" target="_blank" href="https://pl.wikipedia.org/w/index.php?title='+(giga.addUnderscores(h.title))+'&action=edit&redlink=1">UtworzyÄ nowe</a>? '+ 1888 '<a class="standard" target="_blank" href="https://www.google.pl/search?'+makeParams({q: `"${h.title}"`})+'">WyguglaÄ</a>?' : ''; 1889 let descText = ''; 1890 if (hasValue(h.description)) { 1891 descText = '<div class="description">'+h.description+'</div>'; 1892 } else if (hasValue(point.description)) { 1893 descText = '<div class="description">'+point.description+'</div>'; 1894 } 1895 if (point.kind!='adhoc') { 1896 descText += '<span class="lighter">Prawdopodobnie brak takiego hasÅa w Wikipedii.'+wikiPrompt+'</span>'; 1897 } 1898 document.getElementById('wiki').innerHTML = descText; 1899 } 1900 1901 if (hasValue(point.id)) { 1902 const seeAlso = hotspots.findByAttr('i',point.id); 1903 const seeAlsoTexts = []; 1904 if (seeAlso.length>1) { 1905 let lastPan = ''; 1906 seeAlso.forEach( function(e) { 1907 if (e.p != lastPan) { // jezeli dwa razy na jednej pano, wez tylko pierwsze! 1908 lastPan = e.p; 1909 const ppan = hotspots.panoramas[e.p]; 1910 const t = giga.makeHotspotLink(ppan.title, point, e.p, e.h, e.v, 0.02, bearingOnPan); 1911 let dif = ''; 1912 1913 if (hasCoords && giga.advanced) { 1914 const bearingOnOtherPan = giga.hposToAzimuthGeneric(+e.h, +ppan.azimuthleft, +ppan.azimuthright, 1); 1915 const bearingOtherReal = giga.getBearing(+ppan.lat, +ppan.lon, point.lat, point.lon); 1916 let bearingOtherDiff = giga.plusMinus180(bearingOnOtherPan - bearingOtherReal); 1917 dif = ' <small title="różnica miÄdzy azymutem na panoramie a wyliczonym ze wspóÅrzÄdnych" class="'+giga.getBearingErrorClass(bearingOtherDiff)+'">'+(bearingOtherDiff>=0?'+':'')+bearingOtherDiff.toFixed(2)+'°</small> '; 1918 } 1919 1920 if (e.p!=k.state.currentpanorama) seeAlsoTexts.push('<div class="panorama-hotspot-link-line">'+t+dif+'</div>'); 1921 } 1922 }); 1923 $('#infobox').append('<div class="infobox-section see-also other-panoramas"><h2>Inne panoramy z tym obiektem ('+seeAlsoTexts.length+')</h2>'+seeAlsoTexts.join(' ')+'</div>'); 1924 giga.sortOtherPanoramas('bearingdiff',true); 1925 } 1926 } 1927 1928 }, 1929 1930 sortOtherPanoramas: function(datafield, numeric) { 1931 numeric = numeric===true; 1932 const divs = $('.other-panoramas').find('div.panorama-hotspot-link-line').detach(); 1933 [].sort.call(divs, function(a,b) { 1934 let a1 = $(a).find('.panorama-hotspot-link').data(datafield); 1935 let b1 = $(b).find('.panorama-hotspot-link').data(datafield); 1936 if (numeric) { 1937 a1 = +a1; 1938 b1 = +b1; 1939 } 1940 return a1==b1 ? 0 : ( a1>b1 ? 1 : -1 ); 1941 }).appendTo($('.other-panoramas')); 1942 }, 1943 1944 showPointInfo: function(id) { 1945 var title = '', elev = ''; 1946 var info = k.info;
1947 var h = k.hotspot.getItem('newhs'); 1948 1949 if ($('#infobox').data('hs')=='point:'+id) return; 1950 $('#infobox').data('hs','point:'+id); 1951 1952 const point = hotspots.points.get(id) || {}; 1953 1954 giga.showInfoboxTab(); 1955 1956 if (hasValue(point.title)) title = point.title; 1957 title = '<span>'+title+'</span>'; 1958 const hasCoords = hasValue(point.lat, point.lon); 1959 if (hasValue(point.elevation)) elev = ' (' + point.elevation +' m)'; 1960 var infotext = '<b class="hs-header">' + title + elev + '</b>'; 1961 const altDist2 = hasCoords ? Math.round10(giga.getDistance(info.lat,info.lon,point.lat,point.lon),-1) : ''; 1962 var distances = []; 1963 if (hasValue(altDist2)) distances.push(altDist2); 1964 if (distances.length>0) { 1965 infotext = `<abbr title="OdlegÅoÅÄ do obiektu" class="distance">${distances.join('/')} km</abbr> ${infotext}`; 1966 } 1967 if (point.q=='raw') infotext += ' <i class="fab fa-dev" title="Punkt niezweryfikowany!"></i>'; 1968 $('#infobox').html(infotext+'<div id="wiki"></div>').fadeIn('fast'); 1969 1970 if (giga.advanced) { 1971 const extraInfo = []; 1972 1973 // wspóÅrzÄdne punktu 1974 if (hasCoords) { 1975 const openCode = OpenLocationCode.encode(point.lat,point.lon); 1976 extraInfo.push(`<div><span class="download download-point-kml clickable" data-lat="${point.lat}" data-lon="${point.lon}" 1977 data-name="${point.wiki}"><i title="wspóÅrzÄdne • ${openCode}" class="fas fa-fw fa-map-marker-alt"></i> 1978 ${point.lat.toFixed(5)}, ${point.lon.toFixed(5)}</span> <span class="open-mapy-cz clickable" data-lat="${point.lat}" data-lon="${point.lon}" 1979 data-name="${point.names[0]}"><i title="Otwórz mapkÄ mapy.com" class="fas fa-map-marked-alt"></i></span></div>`); 1980 extraInfo.push('<div><i title="pokaż w innych serwisach" class="far fa-fw fa-map"></i> '+ giga.makePointLatLonLinks(point) + '</div>'); 1981 } 1982 if (hasValue(point.height)) { 1983 extraInfo.push(`<div class="ind"><i title="wysokoÅÄ obiektu" class="fas fa-fw fa-long-arrow-alt-up"></i> ${point.height} m</div>`); 1984 } 1985 1986 let profileText; 1987 // azymut punktu 1988 if (hasValue(h)) { 1989 const bearingOnPan = giga.hposToAzimuth(+h.ath); 1990 var azimuthText = `<span class="download download-line-kml clickable standard" data-azimuth="${bearingOnPan}">${bearingOnPan.toFixed(2)}°</span>`;
1991 if (hasCoords) { 1992 const bearingReal = giga.getBearing(info.lat,info.lon,point.lat,point.lon); 1993 let prefix = ''; 1994 if (Math.abs(bearingOnPan-bearingReal)<0.03) { 1995 prefix = '<i title="różnica miÄdzy azymutem na mapie i na zdjÄciu" class="far fa-circle"></i>'; 1996 } else if ((bearingOnPan-bearingReal)>0) { 1997 prefix = '<i title="różnica miÄdzy azymutem na mapie i na zdjÄciu" class="fas fa-arrow-circle-left"></i>'; 1998 } else { 1999 prefix = '<i title="różnica miÄdzy azymutem na mapie i na zdjÄciu" class="fas fa-arrow-circle-right"></i>'; 2000 } 2001 prefix = prefix + ' '; 2002 //if (bearingOnPan-bearingReal>0) prefix = prefix+'+'; 2003 azimuthText += ' (<i title="azymut wynikajÄ cy z mapy" class="far fa-map"></i> <span class="download download-line-kml clickable standard" data-azimuth="'+bearingReal+'">'+bearingReal.toFixed(2)+'°, ' + 2004 prefix + '' +Math.abs(bearingOnPan-bearingReal).toFixed(2)+'°</span>)'; 2005 profileText = document.createElement('span'); 2006 profileText.classList.add('profile'); 2007 profileText.innerHTML = ' profil »'; 2008 profileText.setAttribute('title', 'oblicz i pokaż profil widzialnoÅci do tego punktu'); 2009 profileText.addEventListener('click', async () => giga.lineOfSight.checkFromCurrent(point)); 2010 } 2011 extraInfo.push('<div><i title="azymut" class="far fa-fw fa-compass"></i> '+azimuthText+'</div>'); 2012 //console.log(point); 2013 if (hasCoords && point.elevation) { 2014 const elevationAngle = giga.getElevationAngle(info.lat,info.lon,info.elevation, point.lat,point.lon,point.elevation); 2015 extraInfo.push('<div><i title="kÄ t pionowy" class="fas fa-fw fa-arrows-alt-v"></i> '+elevationAngle.toFixed(2)+'°</div>'); 2016 } 2017 } 2018 2019 if (!!point.range) extraInfo.push('<div class="range" title="rejon"><i class="fas fa-fw fa-map-signs"></i> <span class="zoom-to-range">'+point.range+'</span></div>'); 2020 if (!!point.country && point.country.length>0) extraInfo.push('<div class="country" title="paÅstwo"><i class="far fa-fw fa-flag"></i> '+point.country.map( code => ISOCountryCode(code)).join(', ')+'</div>'); 2021 2022 if (!!point.peakbagger || !!point.summitpost || !!point.mapycz || !!point.osm) { 2023 let sitelinks = ''; 2024 if (!!point.peakbagger) sitelinks += `PB <a target="_blank" href="https://peakbagger.com/peak.aspx?pid=${point.peakbagger}">${point.peakbagger}</a> `; 2025 if (!!point.summitpost) sitelinks += `SP <a target="_blank" href="https://www.summitpost.org/${kebabCase(point.title)}/${point.summitpost}">${point.summitpost}</a> `; 2026 if (!!point.osm) { 2027 sitelinks += `OSM <a target="_blank" href="https://www.openstreetmap.org/node/${point.osm}">${point.osm}</a> `; 2028 if (point.country.includes('SK')) sitelinks += ` <a target="_blank" href="https://poi.oma.sk/n${point.osm}">OMA</a> `; 2029 } 2030 if (!!point.mapycz) { 2031 let [msource, mid] = point.mapycz.split(' '); 2032 sitelinks += `MAPY.COM <a target="_blank" href="https://mapy.com/en/turisticka?source=${msource}&id=${mid}">O</a> <a target="_blank" href="https://mapy.com/en/turisticka?source=${msource}&id=${mid}&gallery=1">G</a> `; 2033 } 2034 if (sitelinks!='') extraInfo.push(`<div class="websites ind"><i class="fas fa-mountain"></i> ${sitelinks}</div>`); 2035 } 2036 2037 $('#infobox').append('<div class="infobox-section extra-info"><h2>Dodatkowe informacje</h2>'+extraInfo.join(' ')+'</div>'); 2038 if (profileText) document.querySelector('#infobox h2')?.appendChild(profileText); 2039 2040 } 2041 2042 if (hasValue(point.id)) { 2043 const pansFromThere = hotspots.findPanoramasByLocation(point.id); 2044 if (pansFromThere.length>0) { 2045 var pansFromThereTexts = []; 2046 pansFromThere.forEach(function(e) { 2047 pansFromThereTexts.push('<div>'+giga.makePanoramaLink(hotspots.panoramas[e].title, e)+'</div>'); 2048 }); 2049 $('#infobox').append('<div class="infobox-section see-also"><h2>Panoramy z tego punktu ('+pansFromThere.length+')</h2>'+pansFromThereTexts.join(' ')+'</div>'); 2050 } 2051 } 2052 2053 if (hasValue(point.wiki)) { 2054 $('#wiki').html('<img src="img/loader30dark.svg" width="160" height="20">'); 2055 giga.loadWiki('#wiki',point.wiki,point?.wikilang); 2056 } 2057 2058 if (hasValue(point.id)) { 2059 const seeAlso = hotspots.findByAttr('i',point.id); 2060 var seeAlsoTexts = []; 2061 if (seeAlso.length>0) { 2062 let lastPan = '';
2063 seeAlso.forEach( function(e) { 2064 if (e.p!=lastPan) { // jezeli dwa razy na jednej pano, wez tylko pierwsze! 2065 lastPan = e.p; 2066 var t = giga.makeHotspotLink(hotspots.panoramas[e.p].title, point, e.p, e.h, e.v, 0.02); 2067 seeAlsoTexts.push(`<div class="panorama-hotspot-link-line">${t}</div>`); 2068 } 2069 }); 2070 $('#infobox').append(`<div class="infobox-section see-also"><h2>Wszystkie panoramy z tym obiektem (${seeAlsoTexts.length})</h2>${seeAlsoTexts.join(' ')}</div>`); 2071 } 2072 } 2073 }, 2074 2075 getRangeLimits: function(func) { 2076 var hsList = giga.currentPanorama.getHotspotsList().filter(func).sort((a,b)=>+a.ath-b.ath); 2077 const ret = {}; 2078 ret.left = +hsList[0].ath; 2079 ret.right = +hsList[hsList.length-1].ath; 2080 console.log(hsList.length + ': ' + hsList[0].id + ' ... ' + hsList[hsList.length-1].id); 2081 hsList = hsList.sort((a,b)=>+a.atv-b.atv); 2082 ret.top = +hsList[0].atv; 2083 ret.bottom = +hsList[hsList.length-1].atv; 2084 return ret; 2085 }, 2086 2087 closeInfobox: function () { 2088 $('#infobox').fadeOut('fast'); 2089 }, 2090 2091 loadPanorama: function(p,h,v,z) { 2092 giga.loader.show(); 2093 if (hasValue(h,v,z)) { 2094 k.call(`loadPanorama('${p}',${h},${v},${z});`); 2095 } else { 2096 k.call(`loadPanorama('${p}');`); 2097 } 2098 }, 2099 2100 onXMLComplete: function() { 2101 console.log('onXMLComplete...'); 2102 giga.message('XML zaÅadowany'); 2103 if (!k.preview.type) { 2104 k.preview.type = 'CYLINDER'; 2105 k.preview.url = `/giga/data/${giga.currentPanorama.getID()}/tiles/preview.jpg`; 2106 } 2107 }, 2108 2109 onLoadComplete: function() { 2110 console.log('onLoadComplete...'); 2111 giga.message('dane zaÅadowane'); 2112 giga.loader.hide(); 2113 giga.currentPanorama.recalculateLevels(); 2114 }, 2115 2116 onNewPano: function() { 2117 giga.search.disable(); 2118 2119 const infobox = document.getElementById('infobox'); 2120 infobox.innerHTML = 'Najedź kursorem na podpisany obiekt, żeby zobaczyÄ wiÄcej informacji na jego temat...'; 2121 infobox.style.display = ''; 2122 2123 document.querySelectorAll('input.reset-on-new-pano, textarea.reset-on-new-pano').forEach(el => el.value = ''); 2124 giga.azimuthCorrection = 0; 2125 k.state.includebearingerror = false; 2126 giga.message('trwa Åadowanie nowej panoramy'); 2127 giga.mapycom.resetMap(); 2128 giga.currentPanorama.hideBearingErrorsChart(); 2129 giga.compass.updateBounds(); 2130 giga.currentPanorama.updateInfo(); 2131 }, 2132 2133 onRemovePano: function() { 2134 giga.currentPanorama.overlayMap.remove(); 2135 giga.currentPanorama.multiSelect.clear(); 2136 giga.lineOfSight.clear(); 2137 giga.resetAllAdjustments(); 2138 $('#addhotspot').html('').hide(); 2139 giga.loader.show(); 2140 giga.peakFinder.close(); 2141 giga.collisions._labelCache = null; 2142 giga.collisions._labelLevels = new WeakMap(); 2143 $('#infobox').html('ÅadujÄ nowÄ panoramÄ...').show(); 2144 }, 2145 2146 onMouseWheel: function(wheeldelta) { 2147 }, 2148 2149 altKeyDown: 0, 2150 ctrlKeyDown: 0, 2151 shiftKeyDown: 0, 2152 2153 altKeyHandler: function(keyCode,state) { 2154 //console.log(keyCode,state); 2155 switch(keyCode) { 2156 case 'Shift': giga.shiftKeyDown = state; break; 2157 case 'Control': giga.ctrlKeyDown = state; break; 2158 case 'Alt': giga.altKeyDown = state; break; 2159 } 2160 }, 2161 2162 message: function(s) { 2163 console.log('MESSAGE: '+s); 2164 $('#head-message').html(s); 2165 return giga; 2166 }, 2167 2168 moveToHotspot: (hsname, showCircle = true) => { 2169 const h = k.hotspot.getItem(hsname); 2170 if (h) window.setTimeout( function() { 2171 let targetFOV = giga.currentPanorama.levels[+h.level]; 2172 targetFOV = targetFOV ? (0.96 * targetFOV) : k.view.fov; 2173 targetFOV = k.view.fov; 2174 const { x: ath, y: atv } = h.ispoly ? h.getcenter() : { x: h.ath, y: h.atv }; 2175 k.tween(k.view, {fov: targetFOV}, 1.0, "default"); 2176 k.tween(k.view, {hlookat: ath, vlookat: atv}, 1.0, "default", () => { if (showCircle) giga.addMovingCircle(ath,atv); }); 2177 }, 100); 2178 }, 2179 2180 addMovingCircle: (ath,atv) => { 2181 const circleName = `circleoverlay`; 2182 const c = k.hotspot.getItem(circleName); 2183 if (c) { 2184 k.stoptween(c, '*'); 2185 k.removehotspot(circleName); 2186 } 2187 console.log('adding hs: ',circleName); 2188 const circle = k.addhotspot(circleName); 2189 circle.loadstyle('circle'); 2190 circle.ath = ath; 2191 circle.atv = atv; 2192 circle.title = 'circle-marker'; 2193 circle.id = 'circle-marker-id' + Date.now(); 2194 2195 k.call(` 2196 tween(hotspot[${circleName}].scale, 1.3, 0.1, easeInOutQuad); 2197 wait(0.1); 2198 tween(hotspot[${circleName}].scale, 0.1, 0.8, easeInOutQuad); 2199 wait(0.6); 2200 tween(hotspot[${circleName}].alpha, 0.0, 0.2, linear); 2201 wait(0.2); 2202 jscall(console.log('removing hs: ${circleName}')); 2203 removehotspot(${circleName}); 2204 `); 2205 }, 2206 2207 moveToHotspotAndClick: (hsname, showCircle = true) => { 2208 const h = k.hotspot.getItem(hsname); 2209 if (h) { 2210 console.log('moveToHotspotAndClick'); 2211 k.call('hotspotOnOver('+h.labelname+','+hsname+');'); 2212 giga.moveToHotspot(hsname, showCircle); 2213 } 2214 }, 2215 2216 reformatDate: function(s,includeWeekday=true) { 2217 const y = s.substr(0,4), m = s.substr(5,2), d = s.substr(8,2); 2218 const date = new Date(y,m-1,d); 2219 return `${includeWeekday ? (giga.weekDays[date.getDay()]+' ') : ''}${d}.${m}.${y}`; 2220 }, 2221 2222 labelsStatus: -1, 2223 2224 setLabelsStatus: function(n) { 2225 if (n==2 && giga.labelsStatus<=1) { 2226 //console.log('SHOW FOR THE FIRST TIME'); 2227 } 2228 giga.labelsStatus = n; 2229 }, 2230 2231 weekDays: [ 2232 'niedziela', 2233 'poniedziaÅek', 2234 'wtorek', 2235 'Åroda', 2236 'czwartek', 2237 'piÄ tek', 2238 'sobota' 2239 ], 2240 2241 setTitle: function(t, g,extrainfo) { 2242 const info = k.info; 2243 document.title = info.title + ' :: gigapanorama'; 2244 2245 const maxlevel = k.image.level.getArray()[k.image.level.count-1]; 2246 const iwidth = +maxlevel.tiledimagewidth;
2247 const iheight = +maxlevel.tiledimageheight; 2248 const mpix = Math.round(iwidth*iheight/(1000000)); 2249 2250 var ude = ''; 2251 var azimuthInfo = '' , refractionInfo = ''; 2252 const udeZoom = info.zoom || 2; 2253 if (hasValue(info.azimuthleft,info.azimuthright,info.lat,info.lon)) { 2254 const udeParams = { 2255 lat: info.lat, 2256 lon: info.lon, 2257 leftbound: info.azimuthleft, 2258 rightbound: info.azimuthright, 2259 zoom: udeZoom, 2260 alt: info.elevation || 'auto', 2261 range: info.range || 250, 2262 //alt: 'auto', 2263 title: info.title, 2264 description: 'wygenerowane via zbooy-giga' 2265 }; 2266 ude = ' <a href="'+giga.generateUdeuschleURL(udeParams)+'" title="wygeneruj panoramÄ caÅoÅci (zoom '+udeZoom+'x)" target="_blank">[ ♦ ]</a>'; 2267 2268 const azimuthRange = (360 + info.azimuthright - info.azimuthleft) % 360; 2269 // const lookAzimuth = (info.azimuthleft + (k.get('view.hlookatrange')/2 + k.get('view.hlookat'))*azimuthRange) % 360; 2270 2271 azimuthInfo = `<div><i title="kÄ t widzenia" class="fas fa-fw fa-eye"></i> ${azimuthRange.toFixed(1)}° (${info.azimuthleft}...${info.azimuthright}°)</div>`; 2272 if (info.refraction) refractionInfo = `<div><i title="wspóÅczynnik refrakcji" class="fas fa-fw fa-sun"></i> ${info.refraction}</div>`; 2273 } 2274 2275 $('header h1 .panorama-title').html(info.title); 2276 2277 $('#panobox').html('').append( 2278 '<div class="infobox-section no-top-border">' + 2279 //'<h2>Lokalizacja</h2>' + 2280 '<div title="miejsce wykonania"><i class="fas fa-fw fa-flag"></i> <b>' + info.location + '</b></div>' + 2281 '<div class="download download-point-kml clickable" data-lat="'+info.lat+'" data-lon="'+info.lon+'"><i title="wspóÅrzÄdne" class="fas fa-fw fa-map-marker-alt"></i> <span title="' + 2282 (formatcoords(+info.lat,+info.lon).format('DD MM ss X', {latLonSeparator: ', ', decimalPlaces: 1})) + 2283 '">' + info.lat + ', ' + info.lon + '</span></div>' + 2284 '<div><i title="wysokoÅÄ n.p.m." class="fas fa-fw fa-level-up-alt"></i> <span title="">' + info.elevation + ' m</span></div>' + 2285 azimuthInfo + 2286 refractionInfo + 2287 '<!--<br><span class="toggle-overlay-map clickable noselect">Pokaż/ukryj mapÄ</span>-->' + 2288 '</div>' 2289 ); 2290 2291 const pansFromThere = hotspots.findPanoramasByLocation(info.location); 2292 if (pansFromThere.length>0) { 2293 let pansFromThereTexts = []; 2294 pansFromThere.forEach(function(e) { 2295 pansFromThereTexts.push(`<div>${giga.makePanoramaLink(hotspots.panoramas[e].title, e)}</div>`); 2296 }); 2297 $('#panobox').append(`<div class="infobox-section see-also"><h2>Wszystkie panoramy z tego punktu (${pansFromThere.length})</h2>${pansFromThereTexts.join(' ')}</div>`); 2298 } 2299 2300 $('#panobox').append(` 2301 <div class="infobox-section"> 2302 <h2>ZdjÄcie</h2> 2303 2304 <div class="ind"> 2305 <i title="czas wykonania zdjÄcia" class="far fa-fw fa-clock"></i> 2306 ${giga.reformatDate(info.date)} ${info.time} 2307 </div> 2308 2309 <div class="ind"> 2310 <i title="rozmiary zdjÄcia" class="far fa-fw fa-image"></i> 2311 ${iwidth}x${iheight} px (${mpix} mpix) 2312 </div> 2313 2314 ${info.comments ? 2315 `<div class="ind"> 2316 <i title="inne" class="fas fa-fw fa-camera"></i> 2317 ${info.comments} 2318 </div>` 2319 : ''} 2320 2321 ${typeof info.copyright !== 'undefined' ? 2322 `<div class="ind"> 2323 <i title="copyright" class="far fa-fw fa-copyright"></i> 2324 ${info.copyright} 2325 </div>` 2326 : ''} 2327 2328 ${typeof info.published !== 'undefined' ? 2329 `<div class="ind"> 2330 <i title="data opublikowania" class="far fa-fw fa-calendar"></i> 2331 ${giga.reformatDate(info.published, false)} 2332 </div>` 2333 : ''} 2334 </div>` 2335 ); 2336 2337 if (info.tags!='') { 2338 $('#panobox').append( 2339 '<div class="infobox-section">' + 2340 '<h2>SÅowa kluczowe</h2>' + 2341 info.tags.split(/, */).map( s => `<span class="clickable tag" data-results-container="tag-results-container" data-tag="${s}">${s}</span>` ).join(', ') + 2342 '<div id="tag-results-container" style="display:none"></div>' + 2343 '</div>' 2344 ); 2345 } 2346 2347 const farthest = giga.currentPanorama.getRichHotspotsList(true).filter(a => hasValue(a.distance)).sort( function(a,b) {return +a.distance==+b.distance ? 0 : (+a.distance<+b.distance ? 1 : -1); } ); 2348 const maxdist = farthest.length > 0 ? `max. dystans: <span class="hs clickable" data-name="${farthest[0].name}">${farthest[0].title}, <b>${farthest[0].distance.toFixed(1)} km</b></span>` : ''; 2349 const hsCount = giga.currentPanorama.getRichHotspotsList().length; 2350 2351 $('#panobox').append( 2352 '<div class="infobox-section">' + 2353 '<h2>Obiekty</h2>' + 2354 '<div class="ind"><i class="fas fa-fw fa-thumbtack"></i> <b>' + hsCount + '</b>
2354 ' + odmien(hsCount,'opisany','opisane','opisanych') + '</div>' + 2355 (!!maxdist ? '<div class="ind"><i class="fas fa-fw fa-binoculars"></i> ' + maxdist + '</div>' : '') + 2356 '<div><br>Lista: ' + 2357 '<span class="list-all-objects clickable" data-sortby="title">alfabetycznie</span> · ' + 2358 '<span class="list-all-objects clickable" data-sortby="bearing">od lewej</span> · ' + 2359 '<span class="list-all-objects clickable" data-sortby="bearing-error">wg bÅÄdu</span> ' + 2360 '</div>' + 2361 '</div>' 2362 ); 2363 2364 $('.generate-pttk-code, .enable-vertical-angle-optimizer').toggle(giga.advanced); 2365 2366 /* 2367 '<div id="panogroup">' + info.group + '</div>' + 2368 '<div id="extrainfo">' + 2369 '<div><a target="_blank" title="Pokaż na OSM" href="https://www.openstreetmap.org/index.html?mlat=' + info.lat + '&mlon=' + info.lon + '&zoom=14">' + info.lat + ', ' + info.lon + '</a>' + ude + '</div>' + 2370 azimuthInfo + 2371 '<div></div>' + 2372 maxdist + 2373 '</div>'); 2374 */ 2375 }, 2376 2377 getCurrentViewParameters: function() { 2378 const info = k.info, view = k.view; 2379 const aleft = giga.hposToAzimuth(view.hlookat-view.hfov/2), aright = giga.hposToAzimuth(view.hlookat+view.hfov/2); 2380 const acenter = giga.hposToAzimuth(view.hlookat), arange = (720+aright-aleft) % 360; 2381 return { 2382 left: aleft, 2383 right: aright, 2384 center: acenter, 2385 range: arange, 2386 level: giga.currentPanorama.getLevel(view.fov) 2387 }; 2388 }, 2389 2390 getCurrentVerticalAngle: function() { 2391 const info = k.info, image = k.image, view = k.view; 2392 const z = (view.vlookat + image.vfov/2) / image.vfov; 2393 const vRange = giga.currentPanorama.interpolateVerticalRange(); 2394 const atop = info.azimuthtop || vRange.azimuthtop; 2395 const abot = info.azimuthbottom || vRange.azimuthbottom; 2396 return +atop + z*(+abot-atop); 2397 }, 2398 2399 generateCurrentViewMapyCz3D: function() { 2400 const info = k.info; 2401 const currentView = giga.getCurrentViewParameters(); 2402 const params = { 2403 x: info.lon, 2404 y: info.lat, 2405 yaw: Math.round(currentView.center - 180), 2406 pitch: -10, 2407 height: info.elevation + 2, 2408 base: 'ophoto', 2409 z: 16 2410 }; 2411 window.open(giga.generateMapyCz3DURL(params)); 2412 }, 2413 2414 generateCurrentViewUde: function() { 2415 const info = k.info; 2416 const currentView = giga.getCurrentViewParameters(); 2417 const azoom = Math.min(30,Math.ceil(0.2*360/currentView.range)); 2418 const udeParams = { 2419 lat: info.lat, 2420 lon: info.lon, 2421 leftbound: currentView.left, 2422 rightbound: currentView.right, 2423 zoom: azoom, 2424 range: info.range || 250, 2425 alt: info.elevation || 'auto', 2426 title: info.title, 2427 description: 'azymut: '+currentView.center.toFixed(2)+'°' 2428 }; 2429 if (info.refraction) udeParams.refraction = info.refraction; 2430 //console.log('ude params: ', udeParams); 2431 window.open(giga.generateUdeuschleURL(udeParams)); 2432 }, 2433 2434 generateWholeViewUde: function() { 2435 const info = k.info; 2436 const udeParams = { 2437 lat: info.lat, 2438 lon: info.lon, 2439 leftbound: info.azimuthleft, 2440 rightbound: info.azimuthright, 2441 zoom: info.zoom || 2, 2442 range: info.range || 250, 2443 alt: info.elevation || 'auto', 2444 //alt: 'auto', 2445 title: info.title, 2446 description: 'wygenerowane via zbooy-giga' 2447 }; 2448 //console.log(udeParams); 2449 if (info.refraction) udeParams.refraction = info.refraction; 2450 window.open(giga.generateUdeuschleURL(udeParams)); 2451 }, 2452 2453 generateWholeViewPeakfinder: function() { 2454 const info = k.info; 2455 const currentView = giga.getCurrentViewParameters(); 2456 // https://www.peakfinder.com/about/resources/api/ 2457 // lat, lng, name, ele, off, azi, alt, fov 2458 const pfParams = { 2459 lat: info.lat, 2460 lng: info.lon, 2461 ele: info.elevation, 2462 azi: giga.hposToAzimuth(currentView.hlookat), 2463 fov: currentView.range.toFixed(1), 2464 date: info.date+'T'+info.time+':00Z', 2465 name: info.title 2466 }; 2467 window.open(giga.generatePeakfinderURL(pfParams)); 2468 }, 2469 2470 generateWholeViewPeakVisor: function() { 2471 const info = k.info; 2472 const currentView = giga.getCurrentViewParameters(); 2473 const azi = giga.hposToAzimuth(currentView.hlookat); 2474 const pvParams = { 2475 lat: info.lat, 2476 lng: info.lon, 2477 alt: info.elevation, 2478 yaw: azi<=180 ? azi.toFixed(2) : (azi-360).toFixed(2), 2479 hfov: currentView.range.toFixed(2) 2480 }; 2481 window.open(giga.generatePeakVisorURL(pvParams)); 2482 }, 2483 2484 /* 2485 savePointCoords(pointid, coords) { 2486 const info = { 2487 pointid: pointid, 2488 coords: coords 2489 }; 2490 $.post("add-coords.html", info, function(data) { 2491 }); 2492 }, 2493 */ 2494 2495 hposToAzimuth: function(hpos) { 2496 const info = k.info, view = k.view; 2497 hpos = hasValue(hpos) ? hpos : view.hlookat; 2498 const azimuthRange = (360 + (+info.azimuthright) - (+info.azimuthleft)) % 360; 2499 const lookAzimuth = (+info.azimuthleft + (view.hlookatrange/2 + hpos)*azimuthRange) % 360; 2500 return lookAzimuth; 2501 }, 2502 2503 azimuthToHpos: function(azimuth) { 2504 const info = k.info, view = k.view; 2505 const azimuthRange = (360 + (+info.azimuthright) - (+info.azimuthleft)) % 360; 2506 const hpos = ((360+azimuth-info.azimuthleft)%360)/azimuthRange - (view.hlookatrange/2); 2507 return hpos; 2508 }, 2509 2510 hposToAzimuthGeneric: function(hpos,azimuthleft,azimuthright,hlookatrange) { 2511 const azimuthRange = (360 + (+azimuthright) - (+azimuthleft)) % 360; 2512 const lookAzimuth = (+azimuthleft + (hlookatrange/2 + hpos)*azimuthRange) % 360; 2513 return lookAzimuth; 2514 }, 2515 2516 downloadViewLineKML: function(lookAzimuth,length) { 2517 length = length || 100000; // distance in metres 2518 const info = k.info; 2519 const name = info.location + ' ' + lookAzimuth.toFixed(2) + '°'; 2520 giga.KML.saveAs(giga.KML.createBearingLine(name,info.lat,info.lon,lookAzimuth,length),giga.currentPanorama.getID()+'-('+lookAzimuth.toFixed(2)+').kml'); 2521 }, 2522 2523 downloadPointKML: function(name,lat,lon) { 2524 giga.KML.saveAs(giga.KML.createPoint({name:name||(lat+', '+lon),lat:lat,lon:lon}),(name||(lat+'-'+lon))+'.kml'); 2525 }, 2526 2527 downloadAllPointsKML: function() { 2528 const points = [];
2529 hotspots.points.forEach( p => { 2530 if (hasValue(p.lat,p.lon)) { 2531 points.push({ 2532 lat: p.lat, 2533 lon: p.lon, 2534 name: p.id 2535 }); 2536 } 2537 }); 2538 const dateStr = new Date().toLocaleDateString('sv').replace(/-/g, ''); 2539 giga.KML.saveAs(giga.KML.createPoints(`points (${points.length})`, points), `points-${dateStr}-${points.length}.kml`); 2540 }, 2541 2542 getSuggested: function(lat, lon, azimuth, azimuthCorrection, count, mindist, maxdist, filterFunc) { 2543 const azimuth2 = (azimuth + azimuthCorrection + 360) % 360; 2544 const info = k.info; 2545 const iLeft = +info.azimuthleft; 2546 const iRight = +info.azimuthright; 2547 const wrapRange = iLeft >= iRight; 2548 2549 const usedIds = new Set(giga.currentPanorama.getHotspotsList().map(h => h.id)); 2550 2551 let objects = giga.getObjectsFromBearing(+info.lat, +info.lon, azimuth2, 500, mindist, maxdist); 2552 2553 objects.forEach(p => { p.used = usedIds.has(p.id); }); 2554 2555 if (typeof filterFunc === 'function') objects = objects.filter(filterFunc); 2556 2557 return objects.slice(0, count).map(p => { 2558 const elev = p.elevation ? `${p.elevation} m` : ''; 2559 const icon = p.diff < 0 ? '<i class="fas fa-fw fa-arrow-circle-left"></i>' 2560 : '<i class="fas fa-fw fa-arrow-circle-right"></i>'; 2561 2562 let cls = 'suggested'; 2563 if (p.used) cls += ' already-used'; 2564 if (p.q === 'raw') cls += ' raw'; 2565 if (wrapRange ? (p.bearing > iRight && p.bearing < iLeft) 2566 : (p.bearing < iLeft || p.bearing > iRight)) cls += ' out-of-range'; 2567 2568 return `<div title="${makeAttr(transliterate(p.id))} (${p.distance.toFixed(3)} km)" class="${cls}" data-bearing="${p.bearing}" data-id="${p.id}">${icon} ${Math.abs(p.diff).toFixed(2)}° <b>${p.id}</b> ${elev} (${p.distance.toFixed(1)} km)</div>`; 2569 }); 2570 }, 2571 2572 getSuggestedOLD: function(lat,lon,azimuth,azimuthCorrection,count,mindist,maxdist,filterFunc) { 2573 const azimuth2 = ((azimuth+azimuthCorrection)+360) % 360; 2574 const hsList = giga.currentPanorama.getHotspotsList(); 2575 var objects = giga.getObjectsFromBearing(+k.info.lat, +k.info.lon, azimuth2, 500, mindist, maxdist); 2576 objects.forEach( (p,i,a) => { 2577 a[i].used = hsList.find( h => h.id==p.id )!==undefined; 2578 }); 2579 if (typeof filterFunc=='function') { 2580 objects = objects.filter(filterFunc); 2581 } 2582 const info = k.info; 2583 const iLeft = +info.azimuthleft, iRight = +info.azimuthright; 2584 objects = objects.slice(0,count); 2585 return suggested = objects.map( function(p) { 2586 //console.log(p); 2587 const elev = !!p.elevation ? (p.elevation+' m') : ''; 2588 const icon = p.diff<0 ? '<i class="fas fa-fw fa-arrow-circle-left"></i>' : '<i class="fas fa-fw fa-arrow-circle-right"></i>'; 2589 const extraStyles = []; 2590 if (p.used) extraStyles.push('already-used'); 2591 if (p.q==='raw') extraStyles.push('raw'); 2592 if (iLeft<iRight) { 2593 if (p.bearing<iLeft || p.bearing>iRight) extraStyles.push('out-of-range'); 2594 } else { 2595 if (p.bearing<iLeft && p.bearing>iRight) extraStyles.push('out-of-range'); 2596 } 2597 return `<div title="${makeAttr(transliterate(p.id))} (${p.distance.toFixed(3)} km)" class="suggested ${extraStyles.join(' ')}" data-bearing="${p.bearing}" data-id="${p.id}"> 2598 ${icon} ${Math.abs(p.diff).toFixed(2)}° 2599 <b>${p.id}</b> ${elev} (${p.distance.toFixed(1)} km) 2600 </div>`; 2601 }); 2602 }, 2603 2604 azimuthCorrection: 0.0, 2605 2606 colorInput: function(el) { 2607 const vv = el.value.trim(); 2608 el.classList.remove('name-found', 'name-not-found'); 2609 if (vv!='') { 2610 el.classList.add( hasValue(hotspots.points.get(vv)) ? 'name-found' : 'name-not-found' ); 2611 } 2612 }, 2613 2614 addNewHotspot: function(mousex,mousey, toh,tov) { 2615 // mousex, mousey nieuzywane?? 2616 //console.log('addNewHotspot ',mousex,mousey, toh,tov); 2617 const azimuth = giga.hposToAzimuth(+toh); 2618 const corrStep = 0.01; 2619 const tohf = (+toh).toFixed(8), tovf = (+tov).toFixed(8); 2620 const atText = 'ath="'+tohf+'" atv="'+tovf+'"'; 2621 const currentID = giga.currentPanorama.getID(); 2622 // var suggested = giga.getSuggested(+k.get('info.lat'),+k.get('info.lon'),azimuth,9); 2623 /* 2624 $('#addhotspot').html( 2625 `<form><b>Dodaj nowy obiekt:</b> 2626 <span id="dist-range"></span> 2627 <div id="azimuth-correction-container" title="Ustaw poprawkÄ azymutu"> 2628 <span class="azimuth-correction" data-correction="-${corrStep}" title="skoryguj w lewo (Shift = x5)">–</span> 2629 <span class="azimuth-correction" data-correction="+${corrStep}" title="skoryguj w prawo (Shift = x5)">+</span> 2630 </div> 2631 <div id="hs-info-line"> 2632 <span class="clickable copy" data-copy=\'${atText}\'><span id="newH">${tohf}</span>, <span id="newV">${tovf}</span></span> 2633 <span title="wygeneruj KML z liniÄ w tym kierunku (${azimuth.toFixed(2)}
2633°)" class="download-line-kml clickable" data-azimuth="${azimuth}">→${azimuth.toFixed(2)}°</span> 2634 </div> 2635 <div class="typeahead__container"><div class="typeahead__field"> 2636 <div class="typeahead__query"><input placeholder="wpisz nazwÄ obiektu" type="text" id="hsname" autocomplete="off" name="qa" class="js-typeahead"></div> 2637 <div class="typeahead__button" style="display:none"><button type="submit"><span class="typeahead__search-icon"></span></button></div> 2638 </div></div> 2639 <input type="hidden" id="hstitle" value=""><input placeholder="uwagi?" type="text" id="hsdesc" list="hsdesc-options"> 2640 <datalist id="hsdesc-options"><option value="down"></option></datalist><span class="addhotspot-options"> 2641 <input type="checkbox" id="label-narrow" title="przeÅam podpis jak najwÄziej (F2)"> 2642 <label for="label-narrow" title="przeÅam podpis jak najwÄziej" style="position:relative;top:-2px">↕</label> 2643 <input type="checkbox" id="label-acronym" title="podpis z samych pierwszych liter (F4)"> 2644 <label for="label-acronym" title="podpis z samych pierwszych liter" style="position:relative;top:-2px">XY</label> 2645 </span><br> 2646 <div id="slider-range-container" title="Ustaw minimalny i maksymalny dystans do obiektu"><div id="slider-range"></div></div> 2647 2648 <div id="filter-points"> 2649 <input type="checkbox" id="unused"${giga.config.get('filter-unused-' + currentID) == 1 ? ' checked' : ''}><label for="unused" title="pokazuj tylko punkty, których nie sÄ oznaczone na bieÅ¼Ä cej panoramie">niewykorzystane</label> 2650 <input type="checkbox" id="peaks"${giga.config.get('filter-peaks-' + currentID) == 1 ? ' checked' : ''}><label for="peaks" title="pokazuj tylko szczyty">szczyty</label> 2651 <input type="checkbox" id="raw"${giga.config.get('filter-raw-' + currentID) == 1 ? ' checked' : ''}><label for="raw" title="uwzglÄdniaj także dodatkowe, surowe zbiory danych o punktach">dodatkowe</label> 2652 </div> 2653 2654 <div id="suggested-points" class=""></div><div id="send" class="active">wyÅlij</div></form> 2655 <div class="close" title="zamknij">X</div>` 2656 ).fadeIn('fast'); 2657 */ 2658 2659 document.getElementById('addhotspot').innerHTML = 2660 `<form><b>Dodaj nowy obiekt:</b> 2661 <span id="dist-range"></span> 2662 <div id="azimuth-correction-container" title="Ustaw poprawkÄ azymutu"> 2663 <span class="azimuth-correction" data-correction="-${corrStep}" title="skoryguj w lewo (Shift = x5)">–</span> 2664 <span class="azimuth-correction" data-correction="+${corrStep}" title="skoryguj w prawo (Shift = x5)">+</span> 2665 </div> 2666 <div id="hs-info-line"> 2667 <span class="clickable copy" data-copy='${atText}'><span id="newH">${tohf}</span>, <span id="newV">${tovf}</span></span> 2668 <span title="wygeneruj KML z liniÄ w tym kierunku (${azimuth.toFixed(2)}°)" class="download-line-kml clickable" data-azimuth="${azimuth}">→${azimuth.toFixed(2)}°</span> 2669 </div> 2670 <div class="typeahead__container"><div class="typeahead__field"> 2671 <div class="typeahead__query"><input placeholder="wpisz nazwÄ obiektu" type="text" id="hsname" autocomplete="off" name="qa" class="js-typeahead"></div> 2672 <div class="typeahead__button" style="display:none"><button type="submit"><span class="typeahead__search-icon"></span></button></div> 2673 </div></div> 2674 <input type="hidden" id="hstitle" value=""><input placeholder="uwagi?" type="text" id="hsdesc" list="hsdesc-options"> 2675 <datalist id="hsdesc-options"><option value="down"></option></datalist><span class="addhotspot-options"> 2676 <input type="checkbox" id="label-narrow" title="przeÅam podpis jak najwÄziej (F2)"> 2677 <label for="label-narrow" title="przeÅam podpis jak najwÄziej" style="position:relative;top:-2px">↕</label> 2678 <input type="checkbox" id="label-acronym" title="podpis z samych pierwszych liter (F4)"> 2679 <label for="label-acronym" title="podpis z samych pierwszych liter" style="position:relative;top:-2px">XY</label> 2680 </span><br> 2681 <div id="slider-range-container" title="Ustaw minimalny i maksymalny dystans do obiektu"><div id="slider-range"></div></div> 2682 2683 <div id="filter-points"> 2684 <input type="checkbox" id="unused"${giga.config.get('filter-unused-' + currentID) == 1 ? ' checked' : ''}><label for="unused" title="pokazuj tylko punkty, których nie sÄ oznaczone na bieÅ¼Ä cej panoramie">niewykorzystane</label> 2685 <input type="checkbox" id="peaks"${giga.config.get('filter-peaks-' + currentID) == 1 ? ' checked' : ''}><label for="peaks" title="pokazuj tylko szczyty">szczyty</label> 2686 <input type="checkbox" id="raw"${giga.config.get('filter-raw-' + currentID) == 1 ? ' checked' : ''}><label for="raw" title="uwzglÄdniaj także dodatkowe, surowe zbiory danych o punktach">dodatkowe</label> 2687 </div> 2688 2689 <div id="suggested-points" class=""></div><div id="send" class="active">wyÅlij</div></form> 2690 <div class="close" title="zamknij">X</div> 2691 `; 2692 2693 // check if visible, move if outside the viewport 2694 const hswin = document.getElementById('addhotspot'), rect = hswin.getBoundingClientRect(); 2695 if (rect.left<0 || rect.left>=window.innerWidth-20) { 2696 hswin.style.left = ''; 2697 hswin.style.right = '20px'; 2698 } 2699 if (rect.top<0 || rect.top>=window.innerHeight-20) { 2700 hswin.style.top = ''; 2701 hswin.style.bottom = '20px'; 2702 } 2703 2704 fadeIn(hswin, 100); 2705 2706 const correctAzimuth = function(corr) { 2707 giga.azimuthCorrection += +corr;
2708 const values = $("#slider-range").slider("option", "values"); 2709 makeSlider(azimuth, values[0], values[1]); 2710 } 2711 const setAzimuth = function(azimuthCorr) { 2712 giga.azimuthCorrection = azimuthCorr; 2713 const values = $("#slider-range").slider("option", "values"); 2714 makeSlider(azimuth, values[0], values[1]); 2715 } 2716 2717 onWheelDirection(document.getElementById('azimuth-correction-container'), 2718 (direction, e) => { 2719 correctAzimuth(direction=='up' ? 0.01 : -0.01); 2720 } 2721 ); 2722 2723 var makeSlider = function(azimuth,min,max) { 2724 const makeFilter = function(p) { 2725 if (p.used && $('#unused').prop('checked')) return false; 2726 if (p.kind!='peak' && $('#peaks').prop('checked')) return false; 2727 if (p.q=='raw' && !$('#raw').prop('checked')) return false; 2728 return true; 2729 } 2730 //console.log('azimuthCorrection',giga.azimuthCorrection); 2731 2732 const distRange = document.getElementById('dist-range'); 2733 distRange.textContent = ` (${min}...${max} km)`; 2734 const span = document.createElement('span'); 2735 span.className = 'azimuth-correction-reset'; 2736 span.textContent = ` ${giga.azimuthCorrection > 0 ? '+' : ''}${giga.azimuthCorrection.toFixed(2)}°`; 2737 if (+giga.azimuthCorrection.toFixed(2) == 0) span.style.opacity = 0; 2738 distRange.appendChild(span); 2739 span.addEventListener('click', () => setAzimuth(0.0)); 2740 // **** 2741 let startingCorrection = giga.azimuthCorrection; 2742 onRightClickDrag(span, { 2743 throttleMs: 10, 2744 onStart: () => { 2745 console.log('drag start'); 2746 startingCorrection = giga.azimuthCorrection; 2747 }, 2748 onDrag: ({ dx, dy, totalDx, totalDy, x, y }) => { 2749 //console.log(dx,dy); 2750 const pixelsPerUnit = 60; 2751 const correction = Math.round(totalDx / pixelsPerUnit) / 100; 2752 if (giga.azimuthCorrection != startingCorrection + correction) setAzimuth(startingCorrection + correction); 2753 } 2754 }); 2755 onWheelDirection(span, 2756 (direction, e) => { 2757 correctAzimuth(direction=='up' ? 0.01 : -0.01); 2758 } 2759 ); 2760 2761 document.getElementById('suggested-points').innerHTML = giga.getSuggested(+k.info.lat,+k.info.lon,azimuth,giga.azimuthCorrection,9,min,max,makeFilter).join('\n'); 2762 $("#suggested-points .suggested").on('mouseover', function() { 2763 const corrAz = (360 + $(this).data('bearing') - giga.azimuthCorrection) % 360; 2764 k.call('addAzimuthLine('+corrAz+')'); 2765 const pp = hotspots.points.get($(this).data('id')); 2766 if (hasValue(pp.lat,pp.lon) && giga.mapycom.isShown()) { 2767 if (giga.mapycom.timeout) window.clearTimeout(giga.mapycom.timeout); 2768 giga.mapycom.timeout = window.setTimeout( () => { 2769 giga.mapycom.flyToWithLine(+pp.lat,+pp.lon, +k.info.lat,+k.info.lon); 2770 giga.currentPanorama.overlayMap.showPoint(+pp.lat,+pp.lon,pp.title); 2771 }, 300); 2772 } 2773 }).on('mouseout', function() { 2774 k.call('removeAzimuthLine()'); 2775 giga.currentPanorama.overlayMap.removePoint(); 2776 if (giga.mapycom.timeout) window.clearTimeout(giga.mapycom.timeout); 2777 }).on('click','.fa-fw', function(ev) { 2778 ev.stopImmediatePropagation(); 2779 ev.preventDefault(); 2780 const par = $(this).parents('.suggested').first(); 2781 const pp = hotspots.points.get(par.data('id')); 2782 const corrAz = (360 + par.data('bearing') - giga.azimuthCorrection) % 360; 2783 const corrHpos = giga.azimuthToHpos(corrAz); 2784 k.call('moveto('+corrHpos+',get(view.vlookat))'); 2785 if (hasValue(pp.lat,pp.lon)) { 2786 giga.currentPanorama.overlayMap.center(+pp.lat,+pp.lon); 2787 } 2788 }); 2789 giga.config.set('range-min-'+giga.currentPanorama.getID(), min); 2790 giga.config.set('range-max-'+giga.currentPanorama.getID(), max); 2791 } 2792 2793 $('#filter-points input').on('change', function(e) { 2794 const values = $("#slider-range").slider("option", "values"); 2795 $("#slider-range").slider("values", [values[0],values[1]]); 2796 giga.config.set('filter-unused-'+giga.currentPanorama.getID(), $('#unused').prop('checked') ? 1 : 0); 2797 giga.config.set('filter-peaks-'+giga.currentPanorama.getID(), $('#peaks').prop('checked') ? 1 : 0); 2798 giga.config.set('filter-raw-'+giga.currentPanorama.getID(), $('#raw').prop('checked') ? 1 : 0); 2799 }); 2800 2801 $("#slider-range").slider({ 2802 range: true, 2803 min: 0, 2804 max: 250, 2805 values: [0, 250], 2806 change: function(event, ui) { 2807 makeSlider(azimuth,ui.values[0],ui.values[1]); 2808 }, 2809 slide: function(event, ui) { 2810 makeSlider(azimuth,ui.values[0],ui.values[1]); 2811 } 2812 }); 2813 const currMin = isNumeric(giga.config.get('range-min-'+giga.currentPanorama.getID())) ? +giga.config.get('range-min-'+giga.currentPanorama.getID()) : 0; 2814 const currMax = isNumeric(giga.config.get('range-max-'+giga.currentPanorama.getID())) ? +giga.config.get('range-max-'+giga.currentPanorama.getID()) : 250; 2815 $("#slider-range").slider("values", [currMin,currMax]); 2816 2817 $(".azimuth-correction").on('click', function(e) { 2818 correctAzimuth(+$(this).data('correction')*(e.shiftKey?5:1)); 2819 }); 2820 const inputEl = document.getElementById('hsname'); 2821 inputEl.focus(); 2822 $('#addhotspot form').on('keydown', 'input', function(e) { 2823 if (e.which == 13) { //enter 2824 $('#addhotspot #send').click(); 2825 $('#addhotspot form').off('keydown', 'input'); 2826 } else if (e.which == 27) { //esc 2827 $('#addhotspot .close').click(); 2828 $('#addhotspot form').off('keydown', 'input'); 2829 } else { 2830 $('input#hstitle').val(''); 2831 } 2832 }); 2833 /* 2834 $('input#hsname').on('input propertychange paste', function() { 2835 giga.colorInput('input#hsname'); 2836 }); 2837 */ 2838 ['input', 'propertychange', 'change', 'paste'].forEach(ev => { 2839 inputEl.addEventListener(ev, () => { 2840 giga.colorInput(inputEl); 2841 }); 2842 }); 2843 2844 2845 // typeahead setup 2846 //console.log('$.typeahead starting...'); 2847 $.typeahead({
2848 input: 'input#hsname', 2849 minLength: 2, 2850 maxItem: 8, 2851 accent: { 2852 from: 'ãà áäâẽèéëêìÃÃ¯Ã®ÃµÃ²Ã³Ã¶Ã´Ã¹ÃºÃ¼Ã»Ã±Ã§Ä ÄÄÅÅóÅżźÄšžÅÄšáéÃýóúÅĺôľäöÄÅů', 2853 to: 'aaaaaeeeeeiiiiooooouuuuncacelnoszzcszndsaeiyourlolaoeru' 2854 }, 2855 display: ["id"], 2856 order: "asc", 2857 source: { 2858 data: hotspots.getAllNames() 2859 }, 2860 template: function (query, item) { 2861 //console.log(query, item); 2862 const info = k.info; 2863 const point = hotspots.points.get(item.id); 2864 var template = '<span>{{id}}</span>'; 2865 if (hasValue(point)) { 2866 //if (hasValue(point.elevation)) template += ` <span class="ele">${point.elevation}</span>`; 2867 if (hasValue(point.lat,point.lon)) { 2868 let extraStyles = ['dist']; 2869 const {distance, bearing} = giga.getDistanceAndBearing(info.lat,info.lon,point.lat,point.lon); 2870 const iLeft = +info.azimuthleft, iRight = +info.azimuthright; 2871 if (iLeft<iRight) { 2872 if (bearing<iLeft || bearing>iRight) extraStyles.push('out-of-range'); 2873 } else { 2874 if (bearing<iLeft && bearing>iRight) extraStyles.push('out-of-range'); 2875 } 2876 template += ` <span class="${extraStyles.join(' ')}">${distance.toFixed(1)} km</span>`; 2877 } 2878 } 2879 template = `<span>${template}</span>`; 2880 return template; 2881 }, 2882 callback: { 2883 onClickBefore: function (node, a, item, event) { 2884 //console.log(node, a, item, event); 2885 if (event.ctrlKey) { 2886 event.stopImmediatePropagation(); 2887 event.preventDefault(); 2888 // move panorama to correct azimuth 2889 const point = hotspots.points.get(item.id); 2890 if (hasValue(point.lat,point.lon)) { 2891 const corrAz = (360 + giga.getBearing(k.info.lat,k.info.lon,point.lat,point.lon) - giga.azimuthCorrection) % 360; 2892 const corrHpos = giga.azimuthToHpos(corrAz); 2893 k.call(`moveto(${corrHpos},get(view.vlookat))`); 2894 giga.currentPanorama.overlayMap.center(+point.lat,+point.lon); 2895 k.call('addAzimuthLine('+corrAz+')'); 2896 window.setTimeout(() => { 2897 k.call('removeAzimuthLine()'); 2898 },2000) 2899 } 2900 giga.showPointInfo(item.id); 2901 } 2902 }, 2903 onMouseEnter: function (node, a, item, event) { 2904 }, 2905 onMouseLeave: function (node, a, item, event) { 2906 }, 2907 onClickAfter: function (node, a, item, event) { 2908 const xtitle = hotspots.points.get(item.id).title; 2909 $('input#hstitle').val(xtitle); 2910 giga.colorInput(inputEl); 2911 }, 2912 onSubmit: function (node, form, item, event) { 2913 event.preventDefault(); 2914 } 2915 } 2916 }); 2917 2918 }, 2919 2920 removeNewHotspot: function() { 2921 k.call('removehotspot(newhs);'); 2922 }, 2923 2924 changeLabelFontSize: (step) => { 2925 const rule = getCssRule('.labelstyle'); 2926 if (rule) { 2927 const current = parseFloat(rule.style.fontSize) || 11; 2928 const next = Math.round(Math.max(1, current + step)); 2929 rule.style.fontSize = next + 'px'; 2930 k.events.dispatch('onviewchange'); 2931 return next; 2932 } 2933 return undefined; 2934 }, 2935 2936 onReadyHandler: function(krpano) { 2937 k = krpano; 2938 }, 2939 2940 keyDownHandler: function(event) { 2941 //console.log(event); 2942 const keycode = event.key; 2943 const showAutolevelsMessage = () => { 2944 showScreenMessage(`${giga.autolevels.getMethodName()} ${Math.round(giga.autolevels.getStrength()*100)}%`, {category: 'autolevels'}); 2945 } 2946 switch (keycode) { 2947 case 'Alt': 2948 case 'Control': 2949 case 'Shift': 2950 giga.altKeyHandler(keycode,1); 2951 break; 2952 2953 case 'PageUp': // page up 2954 k.call('stepZoom(+1)'); 2955 break; 2956 case 'PageDown': // page down 2957 k.call('stepZoom(-1)'); 2958 break; 2959 case 'End': // end 2960 k.call('moveToRightEdge()'); 2961 break; 2962 case 'Home': // home 2963 k.call('moveToLeftEdge()'); 2964 break; 2965 2966 case 'b': // b 2967 case 'B': // b 2968 if (giga.advanced) { 2969 if (typeof k.state.includebearingerror=='undefined' || k.state.includebearingerror=='false') k.state.includebearingerror = false; 2970 k.state.includebearingerror = !k.state.includebearingerror; 2971 if (k.state.includebearingerror) { 2972 showScreenMessage(`pokaż atrybuty: bÅÄ d kÄ towy`, {category: 'bearingerrors'}); 2973 } else { 2974 showScreenMessage(`ukryj atrybuty: bÅÄ d kÄ towy`, {category: 'bearingerrors'}); 2975 } 2976 giga.currentPanorama.generateLabels( {includeBearingError: k.state.includebearingerror} ); 2977 } 2978 break; 2979 case 'c': // c 2980 if (event.ctrlKey) giga.copySnapshot(); 2981 break; 2982 case 'n': // n 2983 showScreenMessage(`czcionka: ${giga.changeLabelFontSize(+1)} px`, {category: 'label-font-size'}); 2984 break; 2985 case 'N': // N 2986 showScreenMessage(`czcionka: ${giga.changeLabelFontSize(-1)} px`, {category: 'label-font-size'}); 2987 break; 2988 case 't': // t 2989 case 'T': // t 2990 k.call('toggleHotspots()'); 2991 break; 2992 case 'v': // v 2993 case 'V': // v 2994 k.layer.getItem('view_layer').visible = !k.layer.getItem('view_layer').visible; 2995 break; 2996 case 'q': // q 2997 case 'Q': // q 2998 giga.currentPanorama.toggleHotspotsLabels(); 2999 //showScreenMessage(`zmiana wyÅwietlanych informacji`); 3000 break; 3001 case 'm': // m 3002 case 'M': // m 3003 //giga.currentPanorama.overlayMap.toggle();
3004 giga.mapycom.toggle(); 3005 break; 3006 case 'o': // o 3007 case 'O': // o 3008 giga.currentPanorama.appendExtraAttribute("mapycz"); 3009 showScreenMessage('pokaż atrybuty: mapy.com', {category: 'mapycom'}); 3010 break; 3011 case 'w': // w 3012 case 'W': // w 3013 giga.currentPanorama.appendExtraAttribute("wiki"); 3014 showScreenMessage('pokaż atrybuty: Wikipedia', {category: 'wiki'}); 3015 break; 3016 case '9': // 9 3017 if (k.device.webgl) { 3018 giga.resetAllAdjustments(); 3019 showScreenMessage('reset filtrów obrazu', {category: 'reset'}); 3020 } 3021 break; 3022 case 'e': // e 3023 case 'E': // e 3024 giga.currentPanorama.toggleBearingErrorsChart(); 3025 break; 3026 case '=': // 3027 if (k.device.webgl) { 3028 giga.sharpen.increase(); 3029 showScreenMessage(`wyostrzanie: ${giga.sharpen.get().toFixed(1)}`, {category: 'sharpen'}); 3030 } 3031 break; 3032 case '-': // 3033 if (k.device.webgl) { 3034 giga.sharpen.decrease(); 3035 showScreenMessage(`wyostrzanie: ${giga.sharpen.get().toFixed(1)}`, {category: 'sharpen'}); 3036 } 3037 break; 3038 3039 case ';': // ; 3040 giga.adjust.decrease('brightness'); 3041 showScreenMessage(`jasnoÅÄ: ${addSign(giga.adjust.get('brightness').toFixed(2))}`, {category: 'brightness'}); 3042 break; 3043 case "'": // ' 3044 giga.adjust.increase('brightness'); 3045 showScreenMessage(`jasnoÅÄ: ${addSign(giga.adjust.get('brightness').toFixed(2))}`, {category: 'brightness'}); 3046 break; 3047 case '[': // [ 3048 giga.adjust.decrease('contrast'); 3049 showScreenMessage(`kontrast: ${giga.adjust.get('contrast').toFixed(1)}`, {category: 'contrast'}); 3050 break; 3051 case ']': // ] 3052 giga.adjust.increase('contrast'); 3053 showScreenMessage(`kontrast: ${giga.adjust.get('contrast').toFixed(1)}`, {category: 'contrast'}); 3054 break; 3055 case '\\': // \ 3056 giga.adjust.reset(); 3057 giga.vibrance.reset(); 3058 showScreenMessage(`reset regulacji kolorów`); 3059 break; 3060 case 's': // s 3061 giga.vibrance.increase(); 3062 showScreenMessage(`jaskrawoÅÄ: ${giga.vibrance.get().toFixed(1)}`, {category: 'vibrance'}); 3063 break; 3064 case 'S': 3065 giga.vibrance.decrease(); 3066 showScreenMessage(`jaskrawoÅÄ: ${giga.vibrance.get().toFixed(1)}`, {category: 'vibrance'}); 3067 break; 3068 case '0': // 0 3069 giga.edge.reset(); 3070 giga.sharpen.reset(); 3071 showScreenMessage('wyostrzanie i podkreÅlanie krawÄdzi wyÅÄ czone', {category: 'edge'}); 3072 break; 3073 case ',': // , 3074 giga.adjust.increase('gamma',1.01); 3075 giga.adjust.increase('brightness',0.09); 3076 giga.adjust.decrease('contrast',1.13); 3077 showScreenMessage(`zwiÄkszenie gammy`, {category: 'gamma'}); 3078 break; 3079 case '.': // . 3080 giga.adjust.decrease('gamma',1.01); 3081 giga.adjust.decrease('brightness',0.09); 3082 giga.adjust.increase('contrast',1.13); 3083 showScreenMessage(`zmniejszenie gammy`, {category: 'gamma'}); 3084 break; 3085 case 'g': // g 3086 giga.grayscale.toggle(); 3087 showScreenMessage(giga.grayscale.get()>0 ? 'skala szaroÅci' : 'kolor'); 3088 break; 3089 case '1': 3090 giga.autolevels.decreaseStrength(); 3091 showAutolevelsMessage(); 3092 break; 3093 case '2': 3094 giga.autolevels.increaseStrength(); 3095 showAutolevelsMessage(); 3096 break; 3097 case '3': 3098 giga.autolevels.toggle(); 3099 showAutolevelsMessage(); 3100 break; 3101 case '5': 3102 giga.autolevels.toggleLuminanceOnly(); 3103 showScreenMessage(`autolevels: ${giga.autolevels.getLuminanceOnly()==1 ? 'tylko luminancja' : 'RGB'}`, {category: 'autolevels'}); 3104 break; 3105 case '4': 3106 giga.autolevels.next(); 3107 showAutolevelsMessage(); 3108 break; 3109 case '$': 3110 giga.autolevels.prev(); 3111 showAutolevelsMessage(); 3112 break; 3113 case '6': 3114 const currentMode = +k.state.hotspotsshowmode; 3115 if (currentMode!=giga.showModes.HIDDEN) { 3116 showScreenMessage(`ukryj podpisy`, {category: 'toggle-hotspots', duration:500}); 3117 k.call(`toggleHotspots(${giga.showModes.HIDDEN})`); 3118 giga.lastShowMode = currentMode; 3119 } else { 3120 showScreenMessage(`pokaż podpisy`, {category: 'toggle-hotspots', duration:500}); 3121 let newMode = typeof giga.lastShowMode=='number' ? giga.lastShowMode : giga.showModes.COLLISION; 3122 k.call(`toggleHotspots(${newMode})`); 3123 } 3124 break; 3125 case 'D': 3126 giga.edge.decrease(); 3127 showScreenMessage(`podkreÅlanie krawÄdzi: ${giga.edge.get().toFixed(2)}`, {category: 'edge'}); 3128 break; 3129 case 'd': 3130 giga.edge.increase(); 3131 showScreenMessage(`podkreÅlanie krawÄdzi: ${giga.edge.get().toFixed(2)}`, {category: 'edge'}); 3132 break; 3133 case 'L': 3134 giga.localcontrast.decreaseStrength(); 3135 showScreenMessage(`lokalny kontrast: ${giga.localcontrast.get().strength.toFixed(1)} / ${giga.localcontrast.get().radius.toFixed(1)} `, {category: 'edge'}); 3136 break; 3137 case 'l': 3138 giga.localcontrast.increaseStrength(); 3139 showScreenMessage(`lokalny kontrast: ${giga.localcontrast.get().strength.toFixed(1)} / ${giga.localcontrast.get().radius.toFixed(1)} `, {category: 'edge'}); 3140 break; 3141 case 'j': 3142 showScreenMessage('zamknij wszystkie poboczne okna');
3143 if (giga.mapycom.isShown) giga.mapycom.toggle(); 3144 giga.lineOfSight.clear(); 3145 giga.removeMiniPanorama(); 3146 document.querySelector('#addhotspot .close')?.click() 3147 break; 3148 case 'h': // h 3149 k.state.ignoremaxzoom = !k.state.ignoremaxzoom; 3150 if (k.state.ignoremaxzoom) { 3151 k.view.savedmaxpixelzoom = k.view.maxpixelzoom; 3152 k.view.maxpixelzoom = 5; 3153 console.log(`maxpixelzoom: ${k.view.maxpixelzoom}`); 3154 showScreenMessage('zoom bez ograniczeÅ', {category: 'zoom-block'}); 3155 } else { 3156 k.view.maxpixelzoom = k.view.savedmaxpixelzoom; 3157 console.log(`maxpixelzoom: ${k.view.maxpixelzoom}`); 3158 showScreenMessage('zoom ograniczony', {category: 'zoom-block'}); 3159 } 3160 break; 3161 } 3162 }, 3163 3164 generateMapyCz3DURL: function(params) { 3165 return 'https://mapy.com/en/zakladni?m3d=1&'+makeParams(params); 3166 }, 3167 3168 generatePeakfinderURL: function (params) { 3169 // https://www.peakfinder.com/about/resources/api/ 3170 // lat, lng, azi, ele, date, fov, name 3171 return 'https://www.peakfinder.com/?cfg=smr&'+makeParams(params); 3172 }, 3173 3174 generatePeakVisorURL: function(params) { 3175 // lat, lng, alt, yaw, hfov 3176 return 'https://peakvisor.com/panorama.html?'+makeParams(params); 3177 }, 3178 3179 generateUdeuschleURL1: function(params) { 3180 /* 3181 lon=8.0713 3182 lat=46.6926 3183 alt=auto 3184 altcam=5 3185 hialt=false 3186 resolution=40 3187 azimut=85 3188 sweep=10 3189 leftbound=80 3190 rightbound=90 3191 split=30 3192 splitnr=1 3193 tilt=auto 3194 tiltsplit=false 3195 elexagg=1.2 3196 range=300 3197 colorcoding=true 3198 colorcodinglimit=134 3199 title=PANO%20TITLE 3200 description=PANO%20DESC 3201 email= 3202 language=en 3203 screenwidth=1920 3204 screenheight=1080 3205 */ 3206 var defaults = { 3207 lat: 50.0, 3208 lon: 20.0, 3209 alt: 'auto', 3210 altcam: 5, 3211 hialt: false, 3212 resolution: 20, 3213 azimut: 180, 3214 sweep: 360, 3215 leftbound: 0, 3216 rightbound: 360, 3217 split: 30, 3218 splitnr: 1, 3219 tilt: 'auto', 3220 tiltsplit: false, 3221 elexagg: 1.2, 3222 range: 250, 3223 refraction: '', 3224 colorcoding: false, 3225 colorcodinglimit: 134, 3226 title: 'Panorama title', 3227 description: 'Panorama description', 3228 email: '', 3229 language: 'en', 3230 screenwidth: screen.width, 3231 screenheight: screen.height 3232 }; 3233 3234 // Merge defaults and options, without modifying defaults 3235 var settings = $.extend( {}, defaults, params ); 3236 3237 settings.sweep = (settings.rightbound - settings.leftbound + 360) % 360; 3238 settings.azimut = (settings.leftbound + settings.sweep/2) % 360; 3239 if (hasValue(settings.zoom)) { 3240 settings.resolution = settings.zoom*20; 3241 delete settings.zoom; 3242 } 3243 if (hasValue(settings.refraction)) { //refraction coefficient, default is 0.13 3244 settings.range += '_RC'+settings.refraction; 3245 delete settings.refraction; 3246 } 3247 settings.title = giga.escapeUnicode(settings.title); 3248 settings.description = giga.escapeUnicode(settings.description); 3249 const p = $.param(settings).replace(/=/g,'%3A').replace(/&/g,'%24%24%24').replace(/%5Cu/g,'%u'); 3250 const url = 'https://www.udeuschle.de/panoramas/panqueryfull.aspx?mode=newstandard&data=' + p; 3251 return url; 3252 }, 3253 3254 generateUdeuschleURL: function(params) { 3255 const defaults = { 3256 lat: 50.0, 3257 lon: 20.0, 3258 alt: 'auto', 3259 altcam: 5, 3260 hialt: false, 3261 resolution: 20, 3262 azimut: 180, 3263 sweep: 360, 3264 leftbound: 0, 3265 rightbound: 360, 3266 split: 30, 3267 splitnr: 1, 3268 tilt: 'auto', 3269 tiltsplit: false, 3270 elexagg: 1.2, 3271 range: 250, 3272 refraction: '', 3273 colorcoding: false, 3274 colorcodinglimit: 134, 3275 title: 'Panorama title', 3276 description: 'Panorama description', 3277 email: '', 3278 language: 'en', 3279 screenwidth: screen.width, 3280 screenheight: screen.height 3281 }; 3282 3283 const settings = { ...defaults, ...params }; 3284 3285 settings.sweep = (settings.rightbound - settings.leftbound + 360) % 360; 3286 settings.azimut = (settings.leftbound + settings.sweep / 2) % 360; 3287 3288 if (hasValue(settings.zoom)) { 3289 settings.resolution = settings.zoom * 20; 3290 delete settings.zoom; 3291 } 3292 if (hasValue(settings.refraction)) { //refraction coefficient, default is 0.13 3293 settings.range += '_RC' + settings.refraction; 3294 delete settings.refraction; 3295 } 3296 3297 settings.title = giga.escapeUnicode(settings.title); 3298 settings.description = giga.escapeUnicode(settings.description); 3299 3300 const p = new URLSearchParams(settings).toString() 3301 .replace(/=/g, '%3A') 3302 .replace(/&/g, '%24%24%24') 3303 .replace(/%5Cu/g, '%u'); 3304
3305 return 'https://www.udeuschle.de/panoramas/panqueryfull.aspx?mode=newstandard&data=' + p; 3306 }, 3307 3308 /* 3309 Based on: 3310 * File: jquery.wikiblurb.js 3311 * Version: 1.0.0 3312 * Description: A simple jQuery plugin to get sections of Wikipedia and other Wikis 3313 * Author: 9bit Studios 3314 * Copyright 2012, 9bit Studios https://www.9bitstudios.com 3315 * Free to use and abuse under the MIT license, https://www.opensource.org/licenses/mit-license.php 3316 */ 3317 3318 loadWiki: async function (selector, article, lang = 'pl') { 3319 if (giga.wikiCache[article]?.status === 'loading') return; 3320 3321 const settings = { 3322 page: giga.addUnderscores(article), 3323 section: 0, 3324 wikiURL: `https://${lang}.wikipedia.org/`, 3325 apiPath: 'w', 3326 removeLinks: true, 3327 customSelector: '', 3328 filterSelector: 'table.infobox' 3329 }; 3330 3331 const object = document.querySelector(selector); 3332 const wLink = ` 3333 <div> 3334 <a title="ArtykuÅ: ${makeAttr(article)}" 3335 target="_blank" 3336 href="${settings.wikiURL}wiki/${settings.page}"> 3337 czytaj wiÄcej na Wikipedii ${lang === 'pl' ? '' : `(${lang})`} » 3338 </a> 3339 </div> 3340 `; 3341 3342 // Cached OK 3343 if (giga.wikiCache[article]?.status === 'ok') { 3344 object.innerHTML = giga.wikiCache[article].data + wLink; 3345 return; 3346 } 3347 3348 giga.wikiCache[article] = { status: 'loading' }; 3349 3350 const url = 3351 `${settings.wikiURL}${settings.apiPath}/api.php?action=parse&format=json&origin=*` + 3352 `&redirects=true${settings.section !== null ? `§ion=${settings.section}` : ''}` + 3353 `&prop=text&disabletoc=true&page=${settings.page}`; 3354 3355 const response = await fetch(url); 3356 3357 if (!response.ok) { 3358 giga.wikiCache[article] = { status: 'error' }; 3359 object.textContent = `wystÄ piÅ problem z zaÅadowaniem artykuÅu z Wikipedii (${response.status})`; 3360 return; 3361 } 3362 3363 const data = await response.json(); 3364 3365 if (data.error) { 3366 giga.wikiCache[article] = { status: 'error' }; 3367 3368 if (data.error.code === 'missingtitle') { 3369 object.innerHTML = 3370 `Na Wikipedii nie znaleziono takiej strony! ` + 3371 `<a class="standard" target="_blank" href="https://www.google.pl/search?${makeParams({ q: article })}">WyguglaÄ</a>?`; 3372 } else { 3373 object.textContent = `wystÄ piÅ bÅÄ d: ${data.error.info}`; 3374 } 3375 return; 3376 } 3377 3378 const markup = data.parse.text["*"]; 3379 3380 const template = document.createElement('template'); 3381 template.innerHTML = `<div class="nbs-wikiblurb">${markup}</div>`; 3382 const blurb = template.content.firstChild; 3383 3384 // Remove links 3385 if (settings.removeLinks) { 3386 blurb.querySelectorAll('a').forEach(a => { 3387 const span = document.createElement('span'); 3388 span.innerHTML = a.innerHTML; 3389 a.replaceWith(span); 3390 }); 3391 } else { 3392 blurb.querySelectorAll('a').forEach(a => { 3393 const href = a.getAttribute('href'); 3394 if (href?.startsWith('/')) { 3395 a.href = settings.wikiURL + href.substring(1); 3396 } 3397 }); 3398 } 3399 3400 // Remove references 3401 blurb.querySelectorAll('sup').forEach(el => el.remove()); 3402 3403 // Remove cite errors 3404 blurb.querySelectorAll('.mw-ext-cite-error').forEach(el => el.remove()); 3405 3406 // Remove filtered elements 3407 if (settings.filterSelector) { 3408 blurb.querySelectorAll(settings.filterSelector).forEach(el => el.remove()); 3409 } 3410 3411 // Extract first <p> and truncate using $.truncate 3412 const firstP = blurb.querySelector('p'); 3413 const truncated = truncateHTML(firstP?.innerHTML || '', { 3414 length: 820, 3415 words: true, 3416 keepFirstWord: true, 3417 finishBlock: false, 3418 ellipsis: ' ...' 3419 }); 3420 3421 blurb.innerHTML = truncated; 3422 3423 giga.wikiCache[article] = { 3424 data: blurb.innerHTML || '?', 3425 status: 'ok' 3426 }; 3427 3428 object.innerHTML = giga.wikiCache[article].data + wLink; 3429 }, 3430 3431 panoramas: {}, 3432 data: {}, 3433 sizes: {}, 3434 points: {}, 3435 3436 loadPoints: function() { 3437 console.warn('giga.loadPoints called'); 3438 $.get('points.xml', 'xml', function(d) { 3439 const allPoints = $(d).find('points').find('point'); 3440 allPoints.each( function(i,p) { 3441 const $p = $(p), w = $p.attr('wiki'); 3442 giga.points[w] = { 3443 name: $p.find('name').text(), 3444 wiki: w, 3445 wikilang: $p.attr('wikilang'), 3446 lat: $p.attr('lat'), 3447 lon: $p.attr('lon'), 3448 ele: $p.attr('elevation'), 3449 kind: $p.attr('kind') 3450 }; 3451 }); 3452 }); 3453 }, 3454 3455 loadPoints2: async function () { 3456 try { 3457 const res = await fetch('points.xml'); 3458 const xmlText = await res.text(); 3459 3460 const xml = new DOMParser().parseFromString(xmlText, "text/xml"); 3461 const points = xml.querySelectorAll('points > point'); 3462
3463 points.forEach(p => { 3464 const w = p.getAttribute('wiki'); 3465 3466 giga.points[w] = { 3467 name: p.querySelector('name')?.textContent ?? "", 3468 wiki: w, 3469 wikilang: p.getAttribute('wikilang'), 3470 lat: p.getAttribute('lat'), 3471 lon: p.getAttribute('lon'), 3472 ele: p.getAttribute('elevation'), 3473 kind: p.getAttribute('kind') 3474 }; 3475 }); 3476 3477 } catch (err) { 3478 console.error("Failed to load points.xml:", err); 3479 } 3480 }, 3481 3482 loadData: async function(pans, onReady) { 3483 await Promise.all(pans.map(async v => { 3484 const d = await fetch(`info/${v}.xml`).then(r => r.text()); 3485 const xml = new DOMParser().parseFromString(d, "text/xml"); 3486 3487 const info = xml.querySelector("info"); 3488 giga.panoramas[v] = { 3489 title: info.getAttribute("title") ?? "", 3490 location: info.getAttribute("location") ?? "", 3491 group: info.getAttribute("group") ?? "", 3492 tags: info.getAttribute("tags") ?? "", 3493 lat: info.getAttribute("lat") ?? "", 3494 lon: info.getAttribute("lon") ?? "", 3495 azimuthleft: info.getAttribute("azimuthleft") ?? "", 3496 azimuthright: info.getAttribute("azimuthright") ?? "", 3497 elevation: info.getAttribute("elevation") ?? "", 3498 date: info.getAttribute("date") ?? "", 3499 time: info.getAttribute("time") ?? "", 3500 published: info.getAttribute("published") ?? "", 3501 comments: info.getAttribute("comments") ?? "", 3502 range: info.getAttribute("range") ?? "", 3503 zoom: info.getAttribute("zoom") ?? "", 3504 refraction: info.getAttribute("refraction") ?? "", 3505 _el: info, 3506 }; 3507 3508 giga.data[v] = Array.from(xml.querySelectorAll("hotspot")); 3509 3510 const img = xml.querySelector("image"); 3511 const str = img ? img.innerHTML.trim() : ""; 3512 const w = [...str.matchAll(/tiledimagewidth="(\d+)"/g)].map(m=>+m[1]).sort((a,b)=>b-a)[0] 3513 || [...str.matchAll(/(\d+)x(\d+)/g)].map(m=>+m[1]).sort((a,b)=>b-a)[0]; 3514 const h = [...str.matchAll(/tiledimageheight="(\d+)"/g)].map(m=>+m[1]).sort((a,b)=>b-a)[0] 3515 || [...str.matchAll(/(\d+)x(\d+)/g)].map(m=>+m[2]).sort((a,b)=>b-a)[0]; 3516 giga.sizes[v] = { width: w || 0, height: h || 0 }; 3517 })); 3518 if (onReady) onReady(); 3519 }, 3520 3521 loader: { 3522 show: function() { 3523 document.querySelector('h1 #homebutton')?.classList.add('rotate-scale-up'); 3524 }, 3525 hide: function() { 3526 document.querySelector('h1 #homebutton')?.classList.remove('rotate-scale-up'); 3527 } 3528 }, 3529 3530 saveSnapshot: function(dataURI, extension = 'jpg') { 3531 // docs: https://krpano.com/docu/plugininterface/#krpano.webGL.makeScreenshot 3532 const azi = giga.hposToAzimuth(k.view.hlookat); 3533 saveAs(dataURItoBlob(dataURI), giga.currentPanorama.getID() + "-" + azi.toFixed(2) + "." + extension); 3534 }, 3535 3536 copySnapshot: async function() { 3537 const itemType = "image/png"; 3538 const dataURI = k.webGL.makeScreenshot(k.webGL.canvas.width, k.webGL.canvas.height, false, 'png'); 3539 if (ClipboardItem.supports(itemType)) { 3540 try { 3541 const data = [new ClipboardItem({ [itemType]: dataURItoBlob(dataURI) })]; 3542 await navigator.clipboard.write(data); 3543 console.log("Copied OK"); 3544 } catch (error) { 3545 console.log(error); 3546 } 3547 } else { 3548 console.log("image/png is not supported"); 3549 } 3550 }, 3551 3552 makeWindowScreenshot: async function() { 3553 const dataURI = await screenshot.takeScreenshot(); 3554 if (dataURI) { 3555 giga.saveSnapshot(dataURI, 'png'); 3556 } 3557 }, 3558 3559 resetAllAdjustments: () => { 3560 giga.adjust.reset(); 3561 giga.sharpen.reset(); 3562 giga.vibrance.reset(); 3563 giga.grayscale.reset(); 3564 giga.autolevels.reset(); 3565 giga.localcontrast.reset(); 3566 giga.temperature.reset(); 3567 //giga.shadowHighlight.reset(); 3568 giga.light.reset(); 3569 giga.edge.reset(); 3570 }, 3571 3572 edge: { 3573 ready: false, 3574 readyHandler: function() { 3575 //console.log('giga.edge.readyHandler'); 3576 if (k.get('plugin[edge]')) giga.edge.ready = true; 3577 }, 3578 set: function(v) { 3579 if (giga.edge.ready) k.plugin.getItem('edge').strength = v; 3580 }, 3581 get: function() { 3582 return +k.plugin.getItem('edge').strength; 3583 }, 3584 getText: function() { 3585 return ' E:'+giga.edge.get().toFixed(2); 3586 }, 3587 reset: function() { 3588 if (giga.edge.ready) giga.edge.set(0); 3589 }, 3590 increase: function(amount = 0.02) { 3591 amount = isNumeric(amount) ? +amount : 0.02; 3592 if (giga.edge.ready) { 3593 giga.edge.set(+giga.edge.get()+amount); 3594 } 3595 }, 3596 decrease: function(amount = 0.02) { 3597 amount = isNumeric(amount) ? +amount : 0.02; 3598 if (giga.edge.ready) { 3599 let newAmount = +giga.edge.get() - amount; 3600 giga.edge.set(newAmount>0 ? newAmount : 0.0); 3601 } 3602 }, 3603 isNonZero: function() { 3604 return giga.edge.get() != 0; 3605 } 3606 }, 3607 3608 sharpen: { 3609 ready: false, 3610 readyHandler: function() { 3611 // console.log('giga.sharpen.readyHandler'); 3612 if (k.get('plugin[sharpen]')) giga.sharpen.ready = true; 3613 }, 3614 set: function(v) { 3615 if (giga.sharpen.ready) k.plugin.getItem('sharpen').strength = v; 3616 }, 3617 get: function() { 3618 return +k.plugin.getItem('sharpen').strength; 3619 }, 3620 getText: function() { 3621 return ' S:'+giga.sharpen.get().toFixed(2); 3622 }, 3623 reset: function() { 3624 if (giga.sharpen.ready) giga.sharpen.set(0); 3625 }, 3626 change: function(amount) { 3627 if (amount>=0) { 3628 giga.sharpen.increase(amount); 3629 } else { 3630 giga.sharpen.decrease(-amount); 3631 } 3632 }, 3633 increase: function(amount) { 3634 amount = isNumeric(amount) ? +amount : 0.2; 3635 if (giga.sharpen.ready) { 3636 giga.sharpen.set(+giga.sharpen.get()+amount); 3637 } 3638 }, 3639 decrease: function(amount) { 3640 amount = isNumeric(amount) ? +amount : 0.2; 3641 if (giga.sharpen.ready) { 3642 giga.sharpen.set(Math.max(0,giga.sharpen.get()-amount)); 3643 } 3644 }, 3645 isNonZero: function() { 3646 return giga.sharpen.get()!=0; 3647 } 3648 }, 3649 3650 grayscale: { 3651 ready: false, 3652 readyHandler: function() { 3653 if (k.get('plugin[grayscale]')) giga.grayscale.ready = true; 3654 }, 3655 set: function(v) { 3656 if (giga.grayscale.ready) k.set('plugin[grayscale].factor',v); 3657 }, 3658 get: function() { 3659 return +k.get('plugin[grayscale].factor'); 3660 }, 3661 reset: function() { 3662 if (giga.grayscale.ready) giga.grayscale.set(0.0); 3663 }, 3664 toggle: function() { 3665 if (giga.grayscale.ready) { 3666 giga.grayscale.set(giga.grayscale.get()>0 ? 0.0 : 1.0); 3667 } 3668 }, 3669 increase: function(amount) { 3670 amount = isNumeric(amount) ? +amount : 0.1; 3671 if (giga.grayscale.ready) { 3672 giga.grayscale.set(Math.min(1.0,+giga.grayscale.get()+amount)); 3673 } 3674 }, 3675 decrease: function(amount) { 3676 amount = isNumeric(amount) ? +amount : 0.1; 3677 if (giga.grayscale.ready) { 3678 giga.grayscale.set(Math.max(0.0,giga.grayscale.get()-amount)); 3679 } 3680 } 3681 }, 3682 3683 3684 autolevels: { 3685 3686 ready: false, 3687 getMethodName: (n) => { 3688 if (typeof n==='undefined') n = giga.autolevels.get(); 3689 switch (n) { 3690 case 0: return 'auto-levels: wyÅÄ czone'; 3691 case 1: return 'auto-levels: histogram equalization'; 3692 //case 2: return 'auto-levels: multiscale retinex'; 3693 case 2: return 'auto-levels: sigmoid'; 3694 case 3: return 'auto-levels: fast sigmoid'; 3695 case 4: return 'auto-levels: CLAHE'; 3696 case 5: return 'auto-levels: tone mapping'; 3697 case 6: return 'auto-levels: histogram stretching'; 3698 } 3699 return 'auto-levels: ???'; 3700 }, 3701 maxMethod: 6, 3702 prevMethod: 2, 3703 readyHandler: function() { 3704 if (k.get('plugin[autolevels]')) giga.autolevels.ready = true; 3705 }, 3706 set: function(method) { 3707 if (giga.autolevels.ready) { 3708 console.log(`autolevels method ${method}`); 3709 if (method>0) giga.autolevels.prevMethod = method; 3710 k.set('plugin[autolevels].method',method); 3711 k.set('plugin[autolevels].enabled',method>0); 3712 } 3713 }, 3714 get: function() { 3715 return +k.get('plugin[autolevels].method'); 3716 }, 3717 setStrength: function(strength) { 3718 if (giga.autolevels.ready) { 3719 console.log(`autolevels strength ${strength}`); 3720 k.set('plugin[autolevels].strength', strength); 3721 } 3722 },
3723 getStrength: function() { 3724 return +k.get('plugin[autolevels].strength'); 3725 }, 3726 setLuminanceOnly: function(luminanceOnly = 0) { 3727 if (giga.autolevels.ready) { 3728 console.log(`autolevels luminanceonly ${luminanceOnly}`); 3729 k.set('plugin[autolevels].luminanceonly', luminanceOnly); 3730 } 3731 }, 3732 getLuminanceOnly: function() { 3733 return +k.get('plugin[autolevels].luminanceonly'); 3734 }, 3735 toggleLuminanceOnly: function() { 3736 giga.autolevels.setLuminanceOnly( giga.autolevels.getLuminanceOnly() == 0 ? 1 : 0 ); 3737 }, 3738 increaseStrength: function(amount) { 3739 amount = isNumeric(amount) ? +amount : 0.1; 3740 if (giga.autolevels.ready) { 3741 giga.autolevels.setStrength(Math.min(1.0,+giga.autolevels.getStrength()+amount)); 3742 } 3743 }, 3744 decreaseStrength: function(amount) { 3745 amount = isNumeric(amount) ? +amount : 0.1; 3746 if (giga.autolevels.ready) { 3747 giga.autolevels.setStrength(Math.max(0.0,giga.autolevels.getStrength()-amount)); 3748 } 3749 }, 3750 reset: function() { 3751 if (giga.autolevels.ready) giga.autolevels.set(0); 3752 }, 3753 next: function() { 3754 if (giga.autolevels.ready) { 3755 giga.autolevels.set((giga.autolevels.get() + 1) % (1+giga.autolevels.maxMethod)); 3756 } 3757 }, 3758 prev: function() { 3759 if (giga.autolevels.ready) { 3760 giga.autolevels.set((giga.autolevels.get() + giga.autolevels.maxMethod) % (1+giga.autolevels.maxMethod)); 3761 } 3762 }, 3763 toggle: function() { 3764 if (giga.autolevels.ready) { 3765 giga.autolevels.set(giga.autolevels.get()>0 ? 0 : giga.autolevels.prevMethod); 3766 } 3767 } 3768 }, 3769 3770 localcontrast: { 3771 3772 ready: false, 3773 readyHandler: function() { 3774 if (k.get('plugin[localcontrast]')) giga.localcontrast.ready = true; 3775 }, 3776 set: function(strength, radius) { 3777 if (giga.localcontrast.ready) { 3778 k.set('plugin[localcontrast].radius', radius); 3779 k.set('plugin[localcontrast].strength', strength); 3780 k.set('plugin[localcontrast].enabled', strength > 1e-5 || radius > 1e-5); 3781 } 3782 }, 3783 setRadius: function(radius) { 3784 if (giga.localcontrast.ready) { 3785 k.set('plugin[localcontrast].radius', radius); 3786 const params = giga.localcontrast.get(); 3787 k.set('plugin[localcontrast].enabled', strength > 1e-5 || radius > 1e-5); 3788 } 3789 }, 3790 setStrength: function(strength) { 3791 if (giga.localcontrast.ready) { 3792 k.set('plugin[localcontrast].strength', strength); 3793 const params = giga.localcontrast.get(); 3794 k.set('plugin[localcontrast].enabled', strength > 1e-5 || radius > 1e-5); 3795 } 3796 }, 3797 get: function() { 3798 return { 3799 strength: +k.get('plugin[localcontrast].strength'), 3800 radius: +k.get('plugin[localcontrast].radius') 3801 }; 3802 }, 3803 reset: function() { 3804 if (giga.localcontrast.ready) giga.localcontrast.set(0,0); 3805 }, 3806 getText: function() { 3807 const params = giga.localcontrast.get(); 3808 return ' LCE:'+params.strength.toFixed(1) + '/'+params.radius.toFixed(1); 3809 }, 3810 increaseStrength: function(amount = 0.5) { 3811 if (giga.localcontrast.ready) { 3812 giga.localcontrast.set(Math.min(20.0, +giga.localcontrast.get().strength+amount), 10.0); 3813 } 3814 }, 3815 decreaseStrength: function(amount = 0.5) { 3816 if (giga.localcontrast.ready) { 3817 giga.localcontrast.set(Math.max(0.0, +giga.localcontrast.get().strength-amount), 10.0); 3818 } 3819 } 3820 3821 }, 3822 3823 vibrance: { 3824 ready: false, 3825 readyHandler: function() { 3826 if (k.get('plugin[vibrance]')) giga.vibrance.ready = true; 3827 }, 3828 set: function(v) { 3829 if (giga.vibrance.ready) k.set('plugin[vibrance].vibrance',v); 3830 }, 3831 get: function() { 3832 return +k.get('plugin[vibrance].vibrance'); 3833 }, 3834 getText: function() { 3835 return ' V:'+giga.vibrance.get().toFixed(1); 3836 }, 3837 reset: function() { 3838 if (giga.vibrance.ready) giga.vibrance.set(1.0); 3839 }, 3840 increase: function(amount) { 3841 amount = isNumeric(amount) ? +amount : 0.1; 3842 if (giga.vibrance.ready) { 3843 giga.vibrance.set(Math.min(20.0,+giga.vibrance.get()+amount)); 3844 } 3845 }, 3846 decrease: function(amount) { 3847 amount = isNumeric(amount) ? +amount : 0.1; 3848 if (giga.vibrance.ready) { 3849 giga.vibrance.set(Math.max(0.0,giga.vibrance.get()-amount)); 3850 } 3851 } 3852 }, 3853 3854 shadowHighlight: { 3855 ready: false, 3856 readyHandler: function() { 3857 if (k.get('plugin[shadowhighlight]')) giga.shadowHighlight.ready = true; 3858 }, 3859 set: function(temp,tint=0) { 3860 if (giga.shadowHighlight.ready) { 3861 } 3862 }, 3863 get: function() { 3864 return { 3865 }; 3866 }, 3867 getText: function() { 3868 }, 3869 reset: function() { 3870 } 3871 }, 3872 3873 temperature: { 3874 ready: false, 3875 readyHandler: function() { 3876 if (k.get('plugin[temperature]')) giga.temperature.ready = true; 3877 }, 3878 set: function(temp,tint=0) { 3879 if (giga.temperature.ready) { 3880 k.set('plugin[temperature].tint',tint); 3881 k.set('plugin[temperature].temperature',temp); 3882 } 3883 }, 3884 get: function() { 3885 return { 3886 temperature: +k.get('plugin[temperature].temperature'), 3887 tint: +k.get('plugin[temperature].tint') 3888 }; 3889 }, 3890 getText: function() { 3891 const params = giga.temperature.get(); 3892 return ' TEMP:'+params.temperature.toFixed(1) + ' TINT:'+params.tint.toFixed(1); 3893 }, 3894 reset: function() { 3895 if (giga.temperature.ready) giga.temperature.set(0,0); 3896 }, 3897 increaseTemperature: function(amount) { 3898 amount = isNumeric(amount) ? +amount : 0.1; 3899 if (giga.temperature.ready) { 3900 console.log('increasing temp...',giga.temperature.get()); 3901 giga.temperature.set(+giga.temperature.get().temperature + amount, +giga.temperature.get().tint); 3902 console.log('increased temp...',giga.temperature.get()); 3903 } 3904 }, 3905 decreaseTemperature: function(amount) { 3906 amount = isNumeric(amount) ? +amount : 0.1; 3907 if (giga.temperature.ready) { 3908 console.log('decreasing temp...',giga.temperature.get()); 3909 giga.temperature.set(+giga.temperature.get().temperature - amount, +giga.temperature.get().tint); 3910 console.log('decreased temp...',giga.temperature.get()); 3911 } 3912 }, 3913 increaseTint: function(amount) { 3914 amount = isNumeric(amount) ? +amount : 0.1; 3915 if (giga.temperature.ready) { 3916 console.log('increasing tint...',giga.temperature.get()); 3917 giga.temperature.set(+giga.temperature.get().temperature, +giga.temperature.get().tint + amount); 3918 console.log('increased tint...',giga.temperature.get()); 3919 } 3920 }, 3921 decreaseTint: function(amount) { 3922 amount = isNumeric(amount) ? +amount : 0.1; 3923 if (giga.temperature.ready) { 3924 console.log('decreasing tint...',giga.temperature.get()); 3925 giga.temperature.set(+giga.temperature.get().temperature, +giga.temperature.get().tint - amount); 3926 console.log('decreased tint...',giga.temperature.get()); 3927 } 3928 } 3929 }, 3930 3931 adjust: { 3932 ready: false, 3933 readyHandler: function() { 3934 // console.log('giga.adjust.readyHandler'); 3935 if (k.get('plugin[adjust]')) giga.adjust.ready = true; 3936 }, 3937 setBrightness: function(b) { 3938 giga.adjust.set('brightness',b); 3939 }, 3940 setContrast: function(c) { 3941 giga.adjust.set('contrast',c); 3942 }, 3943 setGamma: function(g) { 3944 giga.adjust.set('gamma',g); 3945 }, 3946 setAll: function(b,c,g) { 3947 if (giga.adjust.ready) k.call('plugin[adjust].adjust('+b+','+c+','+g+');'); 3948 }, 3949 reset: function() { 3950 if (giga.adjust.ready) giga.adjust.setAll(0,1,1); 3951 }, 3952 set: function(o,v) { 3953 //console.log('setting ',o,v); 3954 if (giga.adjust.ready) k.set('plugin[adjust].'+o,v); 3955 }, 3956 get: function(o) { 3957 return k.get('plugin[adjust].'+o); 3958 }, 3959 getAll: function() { 3960 return { 3961 brightness: giga.adjust.get('brightness'), 3962 contrast: giga.adjust.get('contrast'), 3963 gamma: giga.adjust.get('gamma') 3964 }; 3965 }, 3966 getText: function() { 3967 const b = giga.adjust.get('brightness'), c = giga.adjust.get('contrast'), g = giga.adjust.get('gamma'); 3968 return `B:${b ? b.toFixed(2) : '0'} C:${c ? c.toFixed(2) : '0'} G:${g ? g.toFixed(2) : '0'}`; 3969 }, 3970 increase: function(o, amount) { 3971 var v = giga.adjust.get(o); 3972 //newcolor.r = (pow(color.r,gamma) - 0.5)*contrast + brightness + 0.5; 3973 switch(o) { 3974 case 'brightness': 3975 v += isNumeric(amount) ? +amount : 0.05; 3976 break; 3977 case 'contrast': 3978 v *= isNumeric(amount) ? +amount : 1.1; 3979 break; 3980 case 'gamma': 3981 v *= isNumeric(amount) ? +amount : 1.1; 3982 break; 3983 } 3984 giga.adjust.set(o,v); 3985 //console.log(giga.adjust.getAll()); 3986 }, 3987 decrease: function(o, amount) { 3988 var v = giga.adjust.get(o); 3989 switch(o) { 3990 case 'brightness': 3991 v -= isNumeric(amount) ? +amount : 0.05; 3992 break; 3993 case 'contrast': 3994 v /= isNumeric(amount) ? +amount : 1.1; 3995 break; 3996 case 'gamma': 3997 v /= isNumeric(amount) ? +amount : 1.1; 3998 break; 3999 } 4000 giga.adjust.set(o,v); 4001 //console.log(giga.adjust.getAll()); 4002 }, 4003 isNonZero: function() { 4004 return giga.adjust.get('brightness')!=0 && giga.adjust.get('contrast')!=0 && giga.adjust.get('gamma')!=0; 4005 } 4006 }, 4007 4008 light: { 4009 ready: false, 4010 readyHandler: function() { 4011 if (k.get('plugin[light]')) giga.light.ready = true; 4012 }, 4013 setExposure: function(e=0) { 4014 giga.light.set('exposure',e); 4015 }, 4016 setLights: function(l=0) { 4017 giga.light.set('lights',l); 4018 }, 4019 setShadows: function(s=0) { 4020 giga.light.set('shadows',s); 4021 }, 4022 setAll: function(e,l,s) { 4023 if (giga.light.ready) { 4024 giga.light.set('exposure',e); 4025 giga.light.set('lights',l); 4026 giga.light.set('shadows',s); 4027 } 4028 }, 4029 reset: function() { 4030 if (giga.light.ready) giga.light.setAll(0,0,0); 4031 }, 4032 set: function(o,v) { 4033 if (giga.light.ready) k.set('plugin[light].'+o,v); 4034 }, 4035 get: function(o) { 4036 return k.get('plugin[light].'+o); 4037 }, 4038 getAll: function() { 4039 return { 4040 exposure: giga.light.get('exposure'), 4041 lights: giga.light.get('lights'), 4042 shadows: giga.light.get('shadows') 4043 }; 4044 }, 4045 increase: function(o) { 4046 var v = giga.light.get(o); 4047 switch(o) { 4048 case 'exposure': 4049 v += 0.2;
4050 break; 4051 case 'lights': 4052 v += 0.2; 4053 break; 4054 case 'shadows': 4055 v += 0.2; 4056 break; 4057 } 4058 giga.light.set(o,v); 4059 //console.log(giga.light.getAll()); 4060 }, 4061 decrease: function(o) { 4062 var v = giga.light.get(o); 4063 switch(o) { 4064 case 'exposure': 4065 v -= 0.2; 4066 break; 4067 case 'lights': 4068 v -= 0.2; 4069 break; 4070 case 'shadows': 4071 v -= 0.2; 4072 break; 4073 } 4074 giga.light.set(o,v); 4075 //console.log(giga.light.getAll()); 4076 } 4077 }, 4078 4079 compass: { 4080 container: null, 4081 svg: null, 4082 bearing: 0, 4083 angle: 0, 4084 leftbound: 0, 4085 rightbound: 0, 4086 create: function(sel) { 4087 giga.compass.container = document.querySelector(sel); 4088 if (giga.compass.container) { 4089 giga.compass.container.classList.add('clickable','toggle-overlay-map','noselect'); 4090 giga.compass.container.title = "pokaż/ukryj mapÄ (M)"; 4091 giga.compass.container.innerHTML = ` 4092 <svg id="compass-svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 300 300"> 4093 <circle id="compass-circle" cx="150" cy="150" r="150" /> 4094 <path id="compass-bounds" d="M 150,150 l 150,0 a150,150 0 0,0 150,0 z" fill="yellow" /> 4095 <path id="compass-segment" d="M 150,150 l 150,0 a150,150 0 0,0 150,0 z" fill="green" /> 4096 <text id="compass-bearing" text-anchor="middle" dominant-baseline="central" x="150" y="150" /> 4097 </svg> 4098 `; 4099 giga.compass.svg = document.querySelector('#compass-svg'); 4100 } 4101 return giga.compass; 4102 }, 4103 updateBounds: function(leftbound,rightbound) { 4104 //console.log('updating compass bounds'); 4105 giga.compass.leftbound = leftbound || +k.info.azimuthleft; 4106 giga.compass.rightbound = rightbound || +k.info.azimuthright; 4107 //console.log(giga.compass.leftbound,giga.compass.rightbound); 4108 if (giga.compass.container) { 4109 const angle = (360 + giga.compass.rightbound - giga.compass.leftbound) % 360; 4110 const arc = giga.compass.svg.children['compass-bounds']; 4111 const pii = 0.017453292519943295; 4112 const lastx = 150, lasty = 0, arcType = angle<=180 ? 0 : 1; 4113 const nextx = Math.cos(angle*pii)*150; 4114 const nexty = Math.sin(angle*pii)*150; 4115 const rotangle = giga.compass.rightbound-90; 4116 //console.log('rotation angle '+rotangle); 4117 arc.setAttribute('d', `M 150,150 l 150,0 a150,150 0 ${arcType},0 ${nextx - lastx},${-(nexty - lasty)} z`); 4118 arc.setAttribute('transform', `rotate(${rotangle},150,150)`); 4119 } 4120 return giga.compass; 4121 }, 4122 update: function(angle,bearing) { 4123 if (giga.compass.container) { 4124 giga.compass.bearing = bearing || 0; 4125 giga.compass.angle = angle; 4126 const arc = giga.compass.svg.children['compass-segment']; 4127 const text = giga.compass.svg.children['compass-bearing']; 4128 const pii = 0.017453292519943295; 4129 const lastx = 150, lasty = 0, arcType = angle<=180 ? 0 : 1; 4130 const nextx = Math.cos(angle*pii)*150; 4131 const nexty = Math.sin(angle*pii)*150; 4132 //arc.setAttribute('d', 'M 150,150 l 150,0 a150,150 0 '+arcType+',0 '+(nextx-lastx)+','+(-(nexty-lasty))+' z'); 4133 arc.setAttribute('d', `M 150,150 l 150,0 a150,150 0 ${arcType},0 ${nextx - lastx},${-(nexty - lasty)} z`); 4134 arc.setAttribute('transform', 'rotate('+(giga.compass.angle/2-90+giga.compass.bearing)+',150,150)'); 4135 text.textContent = giga.compass.bearing.toFixed(1)+'°'; 4136 } 4137 return giga.compass; 4138 } 4139 }, 4140 4141 clipboard: { 4142 fallbackCopy: (text) => { 4143 const textarea = document.createElement("textarea"); 4144 textarea.value = text; 4145 textarea.style.position = "fixed"; 4146 textarea.style.top = "0"; 4147 textarea.style.left = "0"; 4148 document.body.appendChild(textarea); 4149 textarea.focus(); 4150 textarea.select(); 4151 try { 4152 document.execCommand("copy"); 4153 } catch (_) { 4154 console.error('document.execCommand("copy") failed'); 4155 } 4156 document.body.removeChild(textarea); 4157 }, 4158 copy: (text) => { 4159 console.log('clipboard copy: ',text); 4160 if (navigator.clipboard && navigator.clipboard.writeText) { 4161 return navigator.clipboard.writeText(text).catch(() => { 4162 console.error('trying fallbackCopy'); 4163 giga.clipboard.fallbackCopy(text); 4164 }); 4165 } else { 4166 console.error('trying fallbackCopy (2)'); 4167 giga.clipboard.fallbackCopy(text); 4168 } 4169 4170 /* 4171 let input = document.createElement('textarea'); 4172 input.value = text; 4173 document.body.appendChild(input); 4174 input.select(); 4175 document.execCommand('copy'); 4176 document.body.removeChild(input); 4177 */ 4178 } 4179 }, 4180 4181 4182 search: { 4183 disable: function() { 4184 const el = document.querySelector('header .js-typeahead'); 4185 el.value = ''; 4186 el.disabled = true; 4187 }, 4188 initialize: function() { 4189 //console.log('initializing search: '+giga.currentPanorama.getID()); 4190 const hslist = giga.currentPanorama.getHotspotsList(); 4191 $.typeahead({ 4192 debug: false, 4193 minLength: 0, 4194 maxItem: 10,
4195 cancelButton: false, 4196 accent: true, 4197 input: "header .js-typeahead", 4198 order: "asc", 4199 display: ["title"], 4200 mustSelectItem: true, 4201 source: hslist, 4202 href: function(e) { 4203 return 'view,'+giga.currentPanorama.getID()+'.html#!'+e.ath+','+e.atv+','+'0.02'; 4204 }, 4205 template: function (query, item) { 4206 const point = hotspots.points.get(item.id); 4207 let poptext = '', template = '<span>{{title}}</span>'; 4208 if (hasValue(point)) { 4209 if (hasValue(point.lat,point.lon)) { 4210 const rh = giga.currentPanorama.richHotspots.find( e => e.id == item.id ); 4211 if (rh && rh?.distance) poptext = `odlegÅoÅÄ: ${rh.distance.toFixed(1)} km, azymut: ${rh.bearingreal.toFixed(2)}°`; 4212 } 4213 if (hasValue(point.elevation)) template += ` <span class="ele">${point.elevation}</span>`; 4214 if (hasValue(point.range)) template += ` <span class="range">${point.range}</span>`; 4215 } 4216 template = `<span title="${poptext}">${template}</span>`; 4217 return template; 4218 }, 4219 callback: { 4220 onSearch: function (node, query) { 4221 //console.log('onSearch'); 4222 //console.log(node, query); 4223 }, 4224 onClickAfter: function (node, a, item, event) { 4225 //console.log('onClickAfter'); 4226 //console.log(item); 4227 giga.moveToHotspotAndClick(item.name); 4228 //console.log(node, a, item, event); 4229 }, 4230 onSubmit: function (node, form, item, event) { 4231 event.preventDefault(); 4232 giga.moveToHotspotAndClick(item.name); 4233 //console.log('onSubmit'); 4234 //console.log(node, form, item, event); 4235 } 4236 }, 4237 emptyTemplate: function (query) { 4238 if (query.length>0) { 4239 return `Nic nie znaleziono dla <i>${escapeXML(query)}</i>`; 4240 } 4241 } 4242 }); 4243 document.querySelector('header').querySelectorAll('.typeahead-hint, .typeahead-result, .typeahead-filter-button, .typeahead-filter').forEach(el => el.remove()); 4244 document.querySelector('input#search').placeholder = `szukaj na panoramie (${hslist.length} ${odmien(hslist.length,'obiekt','obiekty','obiektów')})`; 4245 document.querySelector('header .js-typeahead').disabled = false; 4246 } 4247 }, 4248 4249 onMiniReadyHandler: function(krpano) { 4250 kmini = krpano; 4251 }, 4252 4253 loadMiniPanorama: function(panid,ath,atv,vfov) { 4254 4255 console.log('loadMiniPanorama',panid,ath,atv,vfov); 4256 4257 if ($('#pano-mini').length==0) { 4258 $('<div id="pano-mini" class="draggable resizable"></div>').appendTo($('body')).show(); 4259 $('#pano-mini').resizable({ 4260 handles: 'n, e, s, w, se, sw, nw', 4261 autoHide: true, 4262 cancel: ".no-resize", 4263 minWidth: 180, 4264 minHeight: 120 4265 }); 4266 if (0) $('#pano-mini').draggable({ 4267 cursor: 'move', 4268 cancel: '.no-drag, input, textarea, button, select, option', 4269 scroll: false 4270 }); 4271 } else { 4272 giga.removeMiniPanorama(); 4273 $('#pano-mini').empty().show(); 4274 } 4275 4276 const settings = { 4277 inithlookat: ath, 4278 initvlookat: atv, 4279 initvfov: vfov 4280 }; 4281 4282 embedpano({ 4283 id: "krpano-mini", 4284 xml: "mini.xml", 4285 target: "pano-mini", 4286 html5: "only", 4287 initvars: {dir: panid}, 4288 vars: settings, 4289 mobilescale: 0.5, 4290 passQueryParameters: false, 4291 preserveDrawingBuffer: true, 4292 debugmode: giga.advanced, 4293 showerrors: false, 4294 onready: giga.onMiniReadyHandler, 4295 consolelog: true 4296 }); 4297 }, 4298 4299 removeMiniPanorama: function() { 4300 console.log('removeMiniPanorama'); 4301 try { 4302 removepano('krpano-mini'); 4303 $('#pano-mini').hide(); 4304 } catch(e) { 4305 } 4306 kmini = null; 4307 }, 4308 4309 collisions: { 4310 margin: 5, 4311 delay: 60, 4312 timeoutIndex: 0, 4313 timeoutIDs: [], 4314 isMonitoring: false, 4315 _labelCache: null, 4316 _labelLevels: new WeakMap(), 4317 4318 _rectCache: [], 4319 _rectCount: 0, 4320 _grid: new Map(), 4321 cellSize: 120, // fallback 4322 4323 // --------------------------------------------------------- 4324 // QUICKSELECT (median) â in-place, no allocations 4325 // --------------------------------------------------------- 4326 _quickselect(arr, k, left = 0, right = arr.length - 1) { 4327 while (true) { 4328 if (left === right) return arr[left]; 4329 let pivotIndex = left + ((right - left) >> 1); 4330 pivotIndex = this._partition(arr, left, right, pivotIndex); 4331 if (k === pivotIndex) return arr[k]; 4332 else if (k < pivotIndex) right = pivotIndex - 1; 4333 else left = pivotIndex + 1; 4334 } 4335 }, 4336 4337 _partition(arr, left, right, pivotIndex) { 4338 const pivotValue = arr[pivotIndex]; 4339 [arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]]; 4340 let storeIndex = left; 4341 for (let i = left; i < right; i++) { 4342 if (arr[i] < pivotValue) { 4343 [arr[storeIndex], arr[i]] = [arr[i], arr[storeIndex]]; 4344 storeIndex++; 4345 } 4346 } 4347 [arr[right], arr[storeIndex]] = [arr[storeIndex], arr[right]]; 4348 return storeIndex; 4349 }, 4350 4351 // --------------------------------------------------------- 4352 // COMPUTE CELL SIZE FROM MEDIAN LABEL SIZE 4353 // --------------------------------------------------------- 4354 _computeCellSize(rects, n) { 4355 if (n === 0) return 120; 4356 4357 const widths = new Array(n); 4358 const heights = new Array(n); 4359 4360 for (let i = 0; i < n; i++) { 4361 const r = rects[i]; 4362 widths[i] = r.right - r.left; 4363 heights[i] = r.bottom - r.top; 4364 } 4365 4366 const mid = n >> 1; 4367 4368 const medW = this._quickselect(widths, mid); 4369 const medH = this._quickselect(heights, mid); 4370 4371 const base = Math.max(medW, medH) + this.margin * 2; 4372 4373 // minimal sanity clamp 4374 return base < 40 ? 40 : base; 4375 }, 4376 4377 // --------------------------------------------------------- 4378 // GRID KEY 4379 // --------------------------------------------------------- 4380 _key(x, y) { 4381 return (x << 16) ^ y; 4382 }, 4383 4384 // --------------------------------------------------------- 4385 // BUILD GRID 4386 // --------------------------------------------------------- 4387 _buildGrid(rects, n) { 4388 const grid = this._grid; 4389 grid.clear(); 4390 4391 const cs = this.cellSize; 4392 4393 for (let i = 0; i < n; i++) { 4394 const r = rects[i]; 4395 4396 const x1 = (r.left / cs) | 0; 4397 const y1 = (r.top / cs) | 0; 4398 const x2 = (r.right / cs) | 0; 4399 const y2 = (r.bottom/ cs) | 0; 4400 4401 for (let gx = x1; gx <= x2; gx++) { 4402 for (let gy = y1; gy <= y2; gy++) {
4403 const key = this._key(gx, gy); 4404 let cell = grid.get(key); 4405 if (!cell) { 4406 cell = []; 4407 grid.set(key, cell); 4408 } 4409 cell.push(r); 4410 } 4411 } 4412 } 4413 }, 4414 4415 // --------------------------------------------------------- 4416 // COLLISION CHECK 4417 // --------------------------------------------------------- 4418 _collide(r1, r2, marg) { 4419 return ( 4420 r1.right + marg >= r2.left && 4421 r1.left <= r2.right + marg && 4422 r1.bottom + marg >= r2.top && 4423 r1.top <= r2.bottom + marg 4424 ); 4425 }, 4426 4427 // --------------------------------------------------------- 4428 // GRID COLLISION DETECTION 4429 // --------------------------------------------------------- 4430 calculate(rects, n) { 4431 const out = []; 4432 const marg = this.margin; 4433 const grid = this._grid; 4434 4435 for (const cell of grid.values()) { 4436 const m = cell.length; 4437 if (m < 2) continue; 4438 4439 for (let i = 0; i < m; i++) { 4440 const r1 = cell[i]; 4441 for (let j = i + 1; j < m; j++) { 4442 const r2 = cell[j]; 4443 if (this._collide(r1, r2, marg)) { 4444 out.push([r1, r2]); 4445 } 4446 } 4447 } 4448 } 4449 return out; 4450 }, 4451 4452 // --------------------------------------------------------- 4453 // LABEL CACHE 4454 // --------------------------------------------------------- 4455 getLabels() { 4456 return this._labelCache ?? 4457 Array.from(document.querySelectorAll('#pano .labelstyle')); 4458 }, 4459 4460 clearFlags() { 4461 const labels = this.getLabels(); 4462 for (let i = 0; i < labels.length; i++) { 4463 labels[i].classList.remove('colliding'); 4464 } 4465 }, 4466 4467 // --------------------------------------------------------- 4468 // MAIN GET() 4469 // --------------------------------------------------------- 4470 get() { 4471 const self = this; 4472 const labels = self.getLabels(); 4473 4474 if (self._labelCache === null && labels.length > 0) { 4475 self._labelCache = labels; 4476 self._labelLevels = new WeakMap(); 4477 for (let i = 0; i < labels.length; i++) { 4478 const e = labels[i]; 4479 const lvl = +(e.querySelector('[data-level]')?.dataset.level ?? 0); 4480 self._labelLevels.set(e, lvl); 4481 } 4482 } 4483 4484 const rects = self._rectCache; 4485 let count = 0; 4486 4487 for (let i = 0; i < labels.length; i++) { 4488 const e = labels[i]; 4489 const r = e.getBoundingClientRect(); 4490 if (r.width !== 0) { 4491 const obj = rects[count] || (rects[count] = {}); 4492 obj.left = r.left; 4493 obj.right = r.right; 4494 obj.top = r.top; 4495 obj.bottom = r.bottom; 4496 obj.level = self._labelLevels.get(e); 4497 obj.element = e; 4498 count++; 4499 } 4500 } 4501 4502 self._rectCount = count; 4503 4504 // dynamic cell size from median label size 4505 self.cellSize = self._computeCellSize(rects, count); 4506 4507 // build spatial grid 4508 self._buildGrid(rects, count); 4509 4510 // detect collisions 4511 return self.calculate(rects, count); 4512 }, 4513 4514 // --------------------------------------------------------- 4515 // SHOW 4516 // --------------------------------------------------------- 4517 show() { 4518 //console.log('collisions.show()'); 4519 this.clearFlags(); 4520 const collisions = this.get(); 4521 4522 const curLvl = giga.getCurrentViewParameters().level; 4523 const maxLvl = giga.currentPanorama.levels.at(-1).level; 4524 4525 if (maxLvl !== curLvl || k.state.ignoremaxzoom) { 4526 //console.log('collisions.show() real'); 4527 for (let i = 0; i < collisions.length; i++) { 4528 const [a, b] = collisions[i]; 4529 const which = (a.level > b.level) ? a : b; 4530 const other = (which === a) ? b : a; 4531 4532 if (!other.element.classList.contains('colliding')) { 4533 which.element.classList.add('colliding'); 4534 } 4535 } 4536 } 4537 return collisions; 4538 }, 4539 4540 // --------------------------------------------------------- 4541 // MONITORING 4542 // --------------------------------------------------------- 4543 monitorCallback() { 4544 const self = giga.collisions; 4545 4546 if (self.timeoutIDs.length > 3) { 4547 clearTimeout(self.timeoutIDs.splice(1, 1)[0].id); 4548 } 4549 4550 const idx = self.timeoutIndex++; 4551 const tout = { 4552 idx, 4553 id: setTimeout(() => { 4554 const tIdx = self.timeoutIDs.findIndex(e => e.idx === idx); 4555 if (tIdx >= 0) self.timeoutIDs.splice(tIdx, 1); 4556 self.show(); 4557 }, self.delay) 4558 }; 4559 self.timeoutIDs.push(tout); 4560 }, 4561 4562 monitor(start = true) { 4563 const self = giga.collisions; 4564 4565 if (start) { 4566 if (!self.isMonitoring) { 4567 self._labelCache = null; 4568 self._labelLevels = new WeakMap();
4569 k.events.addListener('onviewchange', self.monitorCallback); 4570 } 4571 self.isMonitoring = true; 4572 } else { 4573 if (self.isMonitoring) { 4574 k.events.removeListener('onviewchange', self.monitorCallback); 4575 self.clearFlags(); 4576 self._labelCache = null; 4577 self._labelLevels = new WeakMap(); 4578 } 4579 self.isMonitoring = false; 4580 } 4581 } 4582 }, 4583 4584 collisionsV5: { 4585 4586 margin: 5, 4587 delay: 60, 4588 timeoutIndex: 0, 4589 timeoutIDs: [], 4590 isMonitoring: false, 4591 _labelCache: null, 4592 _labelLevels: new WeakMap(), 4593 4594 calculate: function(rects) { 4595 const marg = giga.collisions.margin; 4596 const collisions = []; 4597 4598 // filter zero-width rects once, sort by left edge for sweep 4599 const active = rects.filter(r => r.width !== 0).sort((a, b) => a.left - b.left); 4600 4601 const n = active.length; 4602 for (let i = 0; i < n; i++) { 4603 const r1 = active[i]; 4604 const sweepEdge = r1.right + marg; 4605 for (let j = i + 1; j < n; j++) { 4606 const r2 = active[j]; 4607 if (r2.left > sweepEdge) break; // all remaining r2s are further right 4608 if (r1.bottom + marg >= r2.top && r1.top <= r2.bottom + marg) { 4609 collisions.push([r1, r2]); 4610 } 4611 } 4612 } 4613 return collisions; 4614 }, 4615 4616 calculateOLD: function(rectangles) { 4617 const collisions = []; 4618 for (let i = 0; i < rectangles.length; i++) { 4619 for (let j = i + 1; j < rectangles.length; j++) { 4620 if (giga.collisions.isColliding(rectangles[i], rectangles[j])) { 4621 collisions.push([rectangles[i], rectangles[j]]); 4622 } 4623 } 4624 } 4625 return collisions; 4626 }, 4627 4628 getLabels: function() { 4629 return giga.collisions._labelCache ?? Array.from(document.querySelectorAll('#pano .labelstyle')); 4630 }, 4631 4632 clearFlags: function() {
4633 giga.collisions.getLabels().forEach(e => { e.classList.remove('colliding') }); 4634 }, 4635 4636 isColliding: function(rect1, rect2) { 4637 if (rect1.width==0 || rect2.width==0) return false; 4638 const marg = giga.collisions.margin; 4639 return !(rect1.right+marg < rect2.left || rect1.left > rect2.right+marg || rect1.bottom+marg < rect2.top || rect1.top > rect2.bottom+marg); 4640 }, 4641 4642 get: function() { 4643 const self = giga.collisions; 4644 const labels = self.getLabels(); 4645 // populate caches on first get() that actually finds elements 4646 if (self._labelCache === null && labels.length > 0) { 4647 self._labelCache = labels; 4648 self._labelLevels = new WeakMap(); 4649 labels.forEach(e => { 4650 self._labelLevels.set(e, +(e.querySelector('[data-level]')?.dataset.level ?? 0)); 4651 }); 4652 } 4653 const rects = labels.map(e => { 4654 const lvl = self._labelLevels.has(e) 4655 ? self._labelLevels.get(e) 4656 : +(e.querySelector('[data-level]')?.dataset.level ?? 0); 4657 //return Object.assign(e.getBoundingClientRect(), {text: e.innerText, level: lvl, element: e}); 4658 return Object.assign(e.getBoundingClientRect(), {level: lvl, element: e}); 4659 }); 4660 return self.calculate(rects); 4661 }, 4662 4663 show: function() { 4664 //console.time('collisions.show'); 4665 giga.collisions.clearFlags(); 4666 const collisions = giga.collisions.get(); 4667 if (giga.currentPanorama.levels.at(-1).level!=giga.getCurrentViewParameters().level || k.state.ignoremaxzoom) collisions.forEach(els => { 4668 const which = els[0].level>els[1].level ? 0 : 1; 4669 const other = (which+1)%2; 4670 if (!els[other].element.classList.contains('colliding')) { 4671 els[which].element.classList.add('colliding'); 4672 } 4673 }); 4674 //console.timeEnd('collisions.show'); 4675 return collisions; 4676 }, 4677 4678 monitorCallback: function() { 4679 const self = giga.collisions; 4680 if (self.timeoutIDs.length > 3) { 4681 clearTimeout(self.timeoutIDs.splice(1, 1)[0].id); 4682 } 4683 const idx = self.timeoutIndex++; 4684 const tout = {idx: idx, id: setTimeout(() => { 4685 const tIdx = self.timeoutIDs.findIndex(e => e.idx==idx); 4686 if (tIdx >= 0) self.timeoutIDs.splice(tIdx, 1); 4687 self.show(); 4688 }, self.delay)}; 4689 self.timeoutIDs.push(tout); 4690 }, 4691 4692 monitor: function(startMonitoring = true) { 4693 const self = giga.collisions; 4694 if (startMonitoring) { 4695 if (!self.isMonitoring) { 4696 // reset caches â get() will populate on first call once elements exist 4697 self._labelCache = null; 4698 self._labelLevels = new WeakMap(); 4699 k.events.addListener('onviewchange', self.monitorCallback); 4700 } 4701 self.isMonitoring = true; 4702 } else { 4703 if (self.isMonitoring) { 4704 k.events.removeListener('onviewchange', self.monitorCallback); 4705 self.clearFlags(); 4706 self._labelCache = null; 4707 self._labelLevels = new WeakMap(); 4708 } 4709 self.isMonitoring = false; 4710 } 4711 } 4712 4713 }, 4714 4715 4716 collisionsV4: { 4717 4718 margin: 5, 4719 timeoutIndex: 0, 4720 timeoutIDs: [], 4721 delay: 50, 4722 isMonitoring: false, 4723 visibleLabels: new Set(), 4724 _observer: null, 4725 _labelLevels: new WeakMap(), 4726 4727 calculate: function(rects) { 4728 const marg = giga.collisions.margin; 4729 const collisions = []; 4730 const active = rects.filter(r => r.width !== 0) 4731 .sort((a, b) => a.left - b.left); 4732 const n = active.length; 4733 for (let i = 0; i < n; i++) { 4734 const r1 = active[i]; 4735 const sweepEdge = r1.right + marg; 4736 for (let j = i + 1; j < n; j++) { 4737 const r2 = active[j]; 4738 if (r2.left > sweepEdge) break; 4739 if (r1.bottom + marg >= r2.top && r1.top <= r2.bottom + marg) { 4740 collisions.push([r1, r2]); 4741 } 4742 } 4743 } 4744 return collisions; 4745 }, 4746 4747 getLabels: function() { 4748 return document.querySelectorAll('#pano .labelstyle'); 4749 }, 4750 4751 clearFlags: function() {
4752 giga.collisions.getLabels().forEach(e => e.classList.remove('colliding')); 4753 }, 4754 4755 get: function() { 4756 const self = giga.collisions; 4757 const visible = self.visibleLabels; 4758 const labels = visible.size > 0 4759 ? Array.from(visible) 4760 : Array.from(self.getLabels()); 4761 const n = labels.length; 4762 const domRects = new Array(n); 4763 for (let i = 0; i < n; i++) domRects[i] = labels[i].getBoundingClientRect(); 4764 4765 const rects = new Array(n); 4766 for (let i = 0; i < n; i++) { 4767 const e = labels[i]; 4768 const r = domRects[i]; 4769 rects[i] = { left: r.left, right: r.right, top: r.top, bottom: r.bottom, width: r.width, level: self._labelLevels.get(e) ?? 0, element: e }; 4770 } 4771 return self.calculate(rects); 4772 }, 4773 4774 show: function() { 4775 const self = giga.collisions; 4776 const collisions = self.get(); 4777 const atMaxZoom = giga.currentPanorama.levels.at(-1).level === giga.getCurrentViewParameters().level 4778 && !k.state.ignoremaxzoom; 4779 4780 const toCollide = new Set(); 4781 if (!atMaxZoom) { 4782 for (const els of collisions) { 4783 const which = els[0].level > els[1].level ? 0 : 1; 4784 const other = which ^ 1; 4785 if (!els[other].element.classList.contains('colliding')) { 4786 toCollide.add(els[which].element); 4787 } 4788 } 4789 } 4790
4791 self.getLabels().forEach(e => { 4792 if (toCollide.has(e)) { 4793 e.classList.add('colliding'); 4794 } else { 4795 e.classList.remove('colliding'); 4796 } 4797 }); 4798 4799 return collisions; 4800 }, 4801 4802 monitorCallback: function() { 4803 const self = giga.collisions; 4804 if (self.timeoutIDs.length > 3) { 4805 clearTimeout(self.timeoutIDs.splice(1).id); 4806 } 4807 const idx = self.timeoutIndex++; 4808 const tout = { 4809 idx, 4810 id: setTimeout(() => { 4811 const tIdx = self.timeoutIDs.findIndex(e => e.idx === idx); 4812 if (tIdx >= 0) self.timeoutIDs.splice(tIdx, 1); 4813 self.show(); 4814 }, self.delay) 4815 }; 4816 self.timeoutIDs.push(tout); 4817 }, 4818 4819 monitor: function(startMonitoring = true) { 4820 const self = giga.collisions; 4821 const labels = self.getLabels(); 4822 4823 if (startMonitoring) { 4824 if (!self.isMonitoring) { 4825 // cache data-level for each label element once 4826 self._labelLevels = new WeakMap(); 4827 labels.forEach(e => { 4828 const lvl = e.querySelector('[data-level]')?.dataset.level ?? 0; 4829 self._labelLevels.set(e, +lvl); 4830 }); 4831 4832 self._observer = new IntersectionObserver(entries => { 4833 for (const entry of entries) { 4834 if (entry.isIntersecting) { 4835 self.visibleLabels.add(entry.target); 4836 } else { 4837 self.visibleLabels.delete(entry.target); 4838 entry.target.classList.remove('colliding'); 4839 } 4840 } 4841 self.show(); 4842 }, { 4843 root: document.getElementById('pano'), 4844 threshold: 0 4845 }); 4846 4847 labels.forEach(e => self._observer.observe(e)); 4848 k.events.addListener('onviewchange', self.monitorCallback); 4849 } 4850 self.isMonitoring = true; 4851 } else { 4852 if (self.isMonitoring) { 4853 k.events.removeListener('onviewchange', self.monitorCallback); 4854 self._observer.disconnect(); 4855 self._observer = null; 4856 self.visibleLabels.clear(); 4857 self._labelLevels = new WeakMap(); 4858 self.clearFlags(); 4859 } 4860 self.isMonitoring = false; 4861 } 4862 }, 4863 4864 }, 4865 4866 4867 collisionsV3: { 4868 4869 margin: 5, 4870 timeoutIndex: 0, 4871 timeoutIDs: [], 4872 isMonitoring: false, 4873 visibleLabels: new Set(), 4874 _observer: null, 4875 4876 calculate: function(rects) { 4877 const marg = giga.collisions.margin; 4878 const collisions = []; 4879 const active = rects.filter(r => r.width !== 0) 4880 .sort((a, b) => a.left - b.left); 4881 const n = active.length; 4882 for (let i = 0; i < n; i++) { 4883 const r1 = active[i]; 4884 const sweepEdge = r1.right + marg; 4885 for (let j = i + 1; j < n; j++) { 4886 const r2 = active[j]; 4887 if (r2.left > sweepEdge) break; 4888 if (r1.bottom + marg >= r2.top && r1.top <= r2.bottom + marg) { 4889 collisions.push([r1, r2]); 4890 } 4891 } 4892 } 4893 return collisions; 4894 }, 4895 4896 getLabels: function() { 4897 return document.querySelectorAll('#pano .labelstyle'); 4898 }, 4899 4900 clearFlags: function() {
4901 giga.collisions.getLabels().forEach(e => e.classList.remove('colliding')); 4902 }, 4903 4904 get: function() { 4905 const visible = giga.collisions.visibleLabels; 4906 const labels = visible.size > 0 4907 ? Array.from(visible) 4908 : Array.from(giga.collisions.getLabels()); 4909 const n = labels.length; 4910 const domRects = new Array(n); 4911 for (let i = 0; i < n; i++) domRects[i] = labels[i].getBoundingClientRect(); 4912 4913 const rects = new Array(n); 4914 for (let i = 0; i < n; i++) { 4915 const e = labels[i]; 4916 const r = domRects[i]; 4917 const lvl = e.querySelector('[data-level]')?.dataset.level ?? 0; 4918 rects[i] = { left: r.left, right: r.right, top: r.top, bottom: r.bottom, width: r.width, level: +lvl, element: e }; 4919 } 4920 return giga.collisions.calculate(rects); 4921 }, 4922 show: function() { 4923 const self = giga.collisions; 4924 const collisions = self.get(); 4925 const atMaxZoom = self.levels?.at(-1).level === giga.getCurrentViewParameters().level 4926 && !k.state.ignoremaxzoom; 4927 4928 // collect all class changes first, apply in one write pass 4929 const toCollide = new Set(); 4930 const toClear = new Set(); 4931 4932 if (!atMaxZoom) { 4933 for (const els of collisions) { 4934 const which = els[0].level > els[1].level ? 0 : 1; 4935 const other = which ^ 1; 4936 if (!els[other].element.classList.contains('colliding')) { 4937 toCollide.add(els[which].element); 4938 } 4939 } 4940 } 4941 4942 // single write pass: clear all, then add where needed
4943 self.getLabels().forEach(e => { 4944 if (toCollide.has(e)) { 4945 e.classList.add('colliding'); 4946 } else { 4947 e.classList.remove('colliding'); 4948 } 4949 }); 4950 4951 return collisions; 4952 }, 4953 showXX: function() { 4954 giga.collisions.clearFlags(); 4955 4956 const collisions = giga.collisions.get(); 4957 const atMaxZoom = giga.currentPanorama.levels.at(-1).level === giga.getCurrentViewParameters().level 4958 && !k.state.ignoremaxzoom; 4959 if (!atMaxZoom) { 4960 for (const els of collisions) { 4961 const which = els[0].level > els[1].level ? 0 : 1; 4962 const other = which ^ 1; 4963 if (!els[other].element.classList.contains('colliding')) { 4964 els[which].element.classList.add('colliding'); 4965 } 4966 } 4967 } 4968 return collisions; 4969 }, 4970 4971 monitorCallback: function() { 4972 const self = giga.collisions; 4973 if (self.timeoutIDs.length > 3) { 4974 clearTimeout(self.timeoutIDs.splice(1).id); 4975 } 4976 const idx = self.timeoutIndex++; 4977 const tout = { 4978 idx, 4979 id: setTimeout(() => { 4980 const tIdx = self.timeoutIDs.findIndex(e => e.idx === idx); 4981 if (tIdx >= 0) self.timeoutIDs.splice(tIdx, 1); 4982 self.show(); 4983 }, 60) 4984 }; 4985 self.timeoutIDs.push(tout); 4986 }, 4987 4988 monitor: function(startMonitoring = true) { 4989 const self = giga.collisions; 4990 const labels = giga.collisions.getLabels(); 4991 4992 if (startMonitoring) { 4993 if (!self.isMonitoring) { 4994 self._observer = new IntersectionObserver(entries => { 4995 for (const entry of entries) { 4996 if (entry.isIntersecting) { 4997 self.visibleLabels.add(entry.target); 4998 } else { 4999 self.visibleLabels.delete(entry.target); 5000 entry.target.classList.remove('colliding'); 5001 } 5002 } 5003 self.show(); 5004 }, { 5005 root: document.getElementById('pano'), 5006 threshold: 0 5007 }); 5008 labels.forEach(e => self._observer.observe(e)); 5009 k.events.addListener('onviewchange', self.monitorCallback); 5010 } 5011 self.isMonitoring = true; 5012 } else { 5013 if (self.isMonitoring) { 5014 k.events.removeListener('onviewchange', self.monitorCallback); 5015 self._observer.disconnect(); 5016 self._observer = null; 5017 self.visibleLabels.clear(); 5018 self.clearFlags(); 5019 } 5020 self.isMonitoring = false; 5021 } 5022 }, 5023 5024 }, 5025 collisionsV2AI: { 5026 5027 margin: 5, 5028 timeoutIndex: 0, 5029 timeoutIDs: [], 5030 isMonitoring: false, 5031 5032 calculate: function(rects) { 5033 const marg = giga.collisions.margin; 5034 const collisions = []; 5035 5036 // filter zero-width rects once, sort by left edge for sweep 5037 const active = rects.filter(r => r.width !== 0).sort((a, b) => a.left - b.left); 5038 5039 const n = active.length; 5040 for (let i = 0; i < n; i++) { 5041 const r1 = active[i]; 5042 const sweepEdge = r1.right + marg; 5043 for (let j = i + 1; j < n; j++) { 5044 const r2 = active[j]; 5045 if (r2.left > sweepEdge) break; // all remaining r2s are further right 5046 if (r1.bottom + marg >= r2.top && r1.top <= r2.bottom + marg) { 5047 collisions.push([r1, r2]); 5048 } 5049 } 5050 } 5051 return collisions; 5052 }, 5053 5054 calculateN2: function(rects) { 5055 const n = rects.length; 5056 const marg = giga.collisions.margin; 5057 const collisions = []; 5058 for (let i = 0; i < n; i++) { 5059 const r1 = rects[i]; 5060 if (r1.width === 0) continue; 5061 for (let j = i + 1; j < n; j++) { 5062 const r2 = rects[j]; 5063 if (r2.width === 0) continue; 5064 if (r1.right+marg >= r2.left && r1.left <= r2.right+marg && 5065 r1.bottom+marg >= r2.top && r1.top <= r2.bottom+marg) { 5066 collisions.push([r1, r2]); 5067 } 5068 } 5069 } 5070 return collisions; 5071 }, 5072 5073 getLabels: function() { 5074 return document.querySelectorAll('#pano .labelstyle'); 5075 }, 5076 5077 clearFlags: function() {
5078 giga.collisions.getLabels().forEach(e => e.classList.remove('colliding')); 5079 }, 5080 5081 get: function() { 5082 const labels = giga.collisions.getLabels(); 5083 const n = labels.length; 5084 const domRects = new Array(n); 5085 for (let i = 0; i < n; i++) domRects[i] = labels[i].getBoundingClientRect(); 5086 5087 const rects = new Array(n); 5088 for (let i = 0; i < n; i++) { 5089 const e = labels[i]; 5090 const r = domRects[i]; 5091 const lvl = e.querySelector('[data-level]')?.dataset.level ?? 0; 5092 rects[i] = { left: r.left, right: r.right, top: r.top, bottom: r.bottom, width: r.width, level: +lvl, element: e }; 5093 } 5094 return giga.collisions.calculate(rects); 5095 }, 5096 5097 getOLD: function() { 5098 const rects = Array.from(giga.collisions.getLabels()).map(e => { 5099 const lvl = e.querySelector('[data-level]')?.dataset.level ?? 0; 5100 return Object.assign(e.getBoundingClientRect(), { level: +lvl, element: e }); 5101 }); 5102 return giga.collisions.calculate(rects); 5103 }, 5104 5105 show: function() { 5106 giga.collisions.clearFlags(); 5107 const collisions = giga.collisions.get(); 5108 const atMaxZoom = giga.currentPanorama.levels.at(-1).level === giga.getCurrentViewParameters().level 5109 && !k.state.ignoremaxzoom; 5110 if (!atMaxZoom) { 5111 for (const els of collisions) { 5112 const which = els[0].level > els[1].level ? 0 : 1; 5113 const other = which ^ 1; 5114 if (!els[other].element.classList.contains('colliding')) { 5115 els[which].element.classList.add('colliding'); 5116 } 5117 } 5118 } 5119 return collisions; 5120 }, 5121 5122 monitorCallback: function() { 5123 if (giga.collisions.timeoutIDs.length > 3) { 5124 clearTimeout(giga.collisions.timeoutIDs.splice(1).id); 5125 } 5126 const idx = giga.collisions.timeoutIndex++; 5127 const tout = { 5128 idx, 5129 id: setTimeout(() => { 5130 const tIdx = giga.collisions.timeoutIDs.findIndex(e => e.idx === idx); 5131 if (tIdx >= 0) giga.collisions.timeoutIDs.splice(tIdx, 1); 5132 giga.collisions.show(); 5133 }, 60) 5134 }; 5135 giga.collisions.timeoutIDs.push(tout); 5136 }, 5137 5138 monitor: function(startMonitoring = true) { 5139 if (startMonitoring) { 5140 if (!giga.collisions.isMonitoring) { 5141 k.events.addListener('onviewchange', giga.collisions.monitorCallback); 5142 } 5143 giga.collisions.isMonitoring = true; 5144 } else { 5145 if (giga.collisions.isMonitoring) { 5146 k.events.removeListener('onviewchange', giga.collisions.monitorCallback); 5147 giga.collisions.clearFlags(); 5148 } 5149 giga.collisions.isMonitoring = false; 5150 } 5151 } 5152 5153 }, 5154 5155 collisionsV1: { 5156 5157 margin: 5, 5158 timeoutIndex: 0, 5159 timeoutIDs: [], 5160 isMonitoring: false, 5161 5162 calculate: function(rects) { 5163 const marg = giga.collisions.margin; 5164 const collisions = []; 5165 5166 // filter zero-width rects once, sort by left edge for sweep 5167 const active = rects.filter(r => r.width !== 0).sort((a, b) => a.left - b.left); 5168 5169 const n = active.length; 5170 for (let i = 0; i < n; i++) { 5171 const r1 = active[i]; 5172 const sweepEdge = r1.right + marg; 5173 for (let j = i + 1; j < n; j++) { 5174 const r2 = active[j]; 5175 if (r2.left > sweepEdge) break; // all remaining r2s are further right 5176 if (r1.bottom + marg >= r2.top && r1.top <= r2.bottom + marg) { 5177 collisions.push([r1, r2]); 5178 } 5179 } 5180 } 5181 return collisions; 5182 }, 5183 5184 calculateOLD: function(rectangles) { 5185 const collisions = []; 5186 for (let i = 0; i < rectangles.length; i++) { 5187 for (let j = i + 1; j < rectangles.length; j++) { 5188 if (giga.collisions.isColliding(rectangles[i], rectangles[j])) { 5189 collisions.push([rectangles[i], rectangles[j]]); 5190 } 5191 } 5192 } 5193 return collisions; 5194 }, 5195 5196 getLabels: () => { 5197 return document.querySelectorAll('#pano .labelstyle'); 5198 }, 5199 5200 clearFlags: () => {
5201 giga.collisions.getLabels().forEach(e => { e.classList.remove('colliding') }); 5202 }, 5203 5204 isColliding: function(rect1, rect2) { 5205 if (rect1.width==0 || rect2.width==0) return false; 5206 const marg = giga.collisions.margin; 5207 return !(rect1.right+marg < rect2.left || rect1.left > rect2.right+marg || rect1.bottom+marg < rect2.top || rect1.top > rect2.bottom+marg); 5208 }, 5209 5210 get: function() { 5211 const rects = Array.from(giga.collisions.getLabels()).map(e => { 5212 const lvl = e.querySelector('[data-level]')?.dataset.level ?? 0; 5213 //return Object.assign(e.getBoundingClientRect(), {text: e.innerText, level: +lvl, element: e}); 5214 return Object.assign(e.getBoundingClientRect(), {level: +lvl, element: e}); 5215 }); 5216 return giga.collisions.calculate(rects); 5217 }, 5218 5219 show: function() { 5220 //console.time('collisions.show'); 5221 giga.collisions.clearFlags(); 5222 const collisions = giga.collisions.get(); 5223 if (giga.currentPanorama.levels.at(-1).level!=giga.getCurrentViewParameters().level || k.state.ignoremaxzoom) collisions.forEach(els => { 5224 const which = els[0].level>els[1].level ? 0 : 1; 5225 const other = (which+1)%2; 5226 if (!els[other].element.classList.contains('colliding')) { 5227 els[which].element.classList.add('colliding'); 5228 } 5229 }); 5230 //console.timeEnd('collisions.show'); 5231 return collisions; 5232 }, 5233 5234 monitorCallback: function() { 5235 //console.log('giga.collisions.timeoutIDs.length: ',giga.collisions.timeoutIDs.length); 5236 if (giga.collisions.timeoutIDs.length>3) { 5237 //console.log('cleaning'); 5238 clearTimeout(giga.collisions.timeoutIDs.splice(1).id); 5239 } 5240 //console.log('giga.collisions.timeoutIDs.length: ',giga.collisions.timeoutIDs.length); 5241 const idx = giga.collisions.timeoutIndex++; 5242 const tout = {idx: idx, id: setTimeout( () => { 5243 const tIdx = giga.collisions.timeoutIDs.findIndex(e => e.idx==idx); 5244 if (tIdx>=0) giga.collisions.timeoutIDs.splice(tIdx); 5245 giga.collisions.show(); 5246 }, 60)}; 5247 //console.log(tout); 5248 giga.collisions.timeoutIDs.push(tout); 5249 }, 5250 5251 monitor: function(startMonitoring = true) { 5252 if (startMonitoring) { 5253 if (!giga.collisions.isMonitoring) { 5254 k.events.addListener('onviewchange', giga.collisions.monitorCallback); 5255 } 5256 giga.collisions.isMonitoring = true; 5257 } else { 5258 if (giga.collisions.isMonitoring) { 5259 k.events.removeListener('onviewchange', giga.collisions.monitorCallback); 5260 giga.collisions.clearFlags(); 5261 } 5262 giga.collisions.isMonitoring = false; 5263 } 5264 } 5265 5266 }, 5267 5268 futoNEWnotWorking: { 5269 5270 state: 0, 5271 5272 objects: {}, 5273 5274 sources: { 5275 0: '', 5276 5277 // Wysokie 5278 1: 'Ivan BohuÅ¡: Od A po Z o názvoch Vysokých Tatier, TANAP - Tatranská Lomnica 1996', 5279 2: 'Komarnicki Gyula: A Magas-Tátra hegyvilága, Sport Budapest, 1985., wydanie II przepracowane Henrykom Bucsekom', 5280 3: 'Z. RadwaÅska-Paryska, W. H. Paryski: Wielka encyklopedia tatrzaÅskai, Wydawnictwo górskie â Poronin 1995.', 5281 4: 'Farkas Zsolt, Kunos Gábor: A Magas-Tátra szlovák-magyar nevei: ElÅtanulmány a Magas-Tátra gerinctérképéhez, niewydane studium, 1997.', 5282 5: 'Futó Endre: nowa nazwa wprowadzona po raz pierwszy w tej pracy', 5283 6: 'Józef Nyka: Tatry SÅowackie, Przewodnik, Wydanie II, Trawers, Latchorzew 1998; Józef Nyka: Tatry Polskie, Przewodnik, Wydanie X, Trawers, Latchorzew 1998', 5284 7: 'Alfred Grósz: Die Hohe Tatra, Arbeitgemeinschaft der Karpatendeutschen aus Slowakei, Stuttgart 1961', 5285 8: 'Arno PuÅ¡káš: Vysoké Tatry, horolezecký sprievodca, tom I-X, Vydavateľstvo Å port, Bratislava 1957-1989', 5286 9: 'Turistaság és Alpinizmus, 1910-1935', 5287 10: 'Szlovákia autóstérkép magyar településnevekkel, AGÃT Kft. TOP-O-GRÃF Térképészeti Iroda, Budapest 2000.', 5288 11: 'Szontagh Miklós: A Magas Tátra és hegyvidéke, Ãjtátrafüred 1895., wydanie autora', 5289 12: 'Neidenbach Ãkos: nowa nazwa wprowadzona po raz pierwszy w tej pracy lub kolekcja starych nazw z roku 1994', 5290 13: 'Komarnicki Gyula: A Magas-Tátra hegymászókalauza, Wydanie III, Késmárk-Budapest, 1926 Turistaság és Alpinizmus lap-, könyv- és térképkiadó RT',
5291 14: 'Komarnicki Gyula: Hohe Tatra, Késmárk 1918', 5292 15: 'Witold H. Paryski: Tatry Wysokie - Przewodnik Taternicki, tom I-XXIV, Sport i Turystyka, Warszawa, 1951-1988', 5293 16: 'Scheirich László: Emberek, sziklák, századok, Nap kiadó, Dunajská Streda 2001.', 5294 17: 'Klaus Gattinger: Die Hohe Tatra, ViViT s.r.o., Kieżmarok 2003.', 5295 18: 'WÅadysÅaw CywiÅski: Tatry, przewodnik szczegóÅowy, tom 1â19, Poronin 1994â2014', 5296 19: 'Andrzej Z. Górski: korekta jÄzykowa czÄÅci polskiej', 5297 20: 'Jan KieÅkowski: Tatry Wysokie, czÄÅÄ 1-2, Wydawnictwo EXPLO, Gliwice, 1997; ; czÄÅÄ 3, Antykwariat Górski Filar, Kielce 2013.', 5298 21: 'Wildner Dénes: A történelmi Magyarország egykori területeinek helynévtára, Verlag Ungarisches Institut, München 1996', 5299 22: 'Ivan BohuÅ¡: Rozprávanie o názvoch a o prvých výstupoch na tatranské vrcholy, Vydavateľstvo I&B, Tatranská Lomnica, 2003.', 5300 23: 'Kolbenheyer Károly, Kovács Pál: A Magas Tátra, Teschen, 1882.', 5301 24: 'Bácskai Gusztáv: korekta jÄzykowa czÄÅci wÄgierskiej', 5302 25: 'Alfred Grósz: Die Benennungen der Ãstlichen Tatra, niewydany rÄkopis, Kieżmarok', 5303 26: 'MiloÅ¡ JanoÅ¡ka: Sprievodca po Vysokých Tatrách, wydanie druge ponownie, wydal F. KlimeÅ¡ v Liptovskim ÅwiÄtym Mikulaszy, 1923.', 5304 27: 'MiloÅ¡ JanoÅ¡ka: Sprievodca po Tatrách, wydal F. KlimeÅ¡ v Liptovskim ÅwiÄtym Mikulaszy, 1911.', 5305 28: 'Adam Piechowski: GraÅ Tatr, Wydawnictwo TEXT, Warszawa 1992.', 5306 29: 'Nagy Ãrpád: korekta jÄzykowa czÄÅci wÄgierskiej', 5307 30: 'Marian JurÃk: leÅnik Tatranskiego Narodowego Parku', 5308 31: 'Karel Drož: Tatry, wydal F. KlimeÅ¡ v Rużomberku, 1897; Karel Drož: Život na Tatrách, Rokycany 1906; Karel Drož: Z naÅ¡ich Tater, Użhorod 1924', 5309 32: 'MaroÅ¡ BobovÄák, MaroÅ¡ Jacina: Vysoké Tatry â zachodnia czeÅÄ, przewodnik taternicki, Szczyrbskie Jezioro 2010; MaroÅ¡ BobovÄák, MaroÅ¡ Jacina: Vysoké Tatry â wschodnia czeÅÄ, przewodnik taternicki, Szczyrbskie Jezioro 2011', 5310 33: 'Pavol JackoviÄ: Slavkovský Å¡tÃt, horolezecký sprievodca, Poprad 2014;', 5311 34: 'Ivan BohuÅ¡ sen. i Ivan BohuÅ¡ jun.: PrÃbehy zemepisných názvov Vysokých Tatier, Wydawnictwo IB Vysoké Tatry s.r.o., Åomnica TatrzaÅska 2018', 5312 35: 'Jozef GurnÃk, Krywan, niewydane studium', 5313 5314 // Bielskie 5315 1001: 'WÅadysÅaw CywiÅski: Tatry, przewodnik szczegóÅowy, IV. i V. tom, Poronin 1997.', 5316 1002: 'Tadeusz Szczerba: Tatry Bielskie, Monografia, Przewodnik, Wydawnictwo Ryszard M. Remiszewski â RMR, Gliwice 1997.', 5317 1003: 'Z. RadwaÅska-Paryska, W. H. Paryski: Wielka encyklopedia tatrzaÅskai, Wydawnictwo górskie â Poronin 1995.', 5318 1004: 'Witold H. Paryski: Tatry Wysokie â Przewodnik Taternicki, 24. tom, Wydawnictwo Sport i Turystyka, Warszawa, 1951-1988.', 5319 1005: 'Futó Endre: nowa nazwa wprowadzona po raz pierwszy w tej pracy', 5320 1006: 'StanisÅaw Eljasz-Radzikowski: Tatry Bielskie, Pamietnik Towarzystwa TatrzaÅskiego, Kraków 1893 és 1894.', 5321 1007: 'Józef Nyka: Tatry SÅowackie, Przewodnik, Wydanie VI, Trawers, Latchorzew 1998.', 5322 1008: 'Klaus Gattinger: Die Hohe Tatra, ViViT s.r.o., Kieżmarok 2003.', 5323 1009: 'Alfred Grósz: Die Benennungen der Ãstlichen Tatra, niewydany rÄkopis, Kieżmarok', 5324 10010: 'Szontagh Miklós: A Magas Tátra és hegyvidéke, Ãjtátrafüred 1895., wydanie autora', 5325 10011: 'Kolbenheyer Károly, Kovács Pál: A Magas Tátra, Teschen, 1882.', 5326 10012: 'Hefty Gyula Andor és Vigyázó János: A Magas Tátra részletes kalauza, Turistaság és Alpinizmus kiadása, Budapest 1917.', 5327 10013: 'Ivan BohuÅ¡: Od A po Z o názvoch Vysokých Tatier, TANAP â Tatranská Lomnica 1996.', 5328 10014: 'Komarnicki Gyula: A Magas-Tátra hegyvilága, Sport Budapest, 1985., wydanie II przepracowane Henrykem Bucsekom', 5329 10015: 'Tatra Wysokie sÅowackie i polskie, mapa turystyczna 1:25000, Wydawnictwo Kartograficzne Polkart, Warszawa-Zielona Góra-Zakopane, 2003/2004; Vysoké Tatry, turistická mapa 1:25000, Sygnatura, 6. vydanie, 2010; Magas-Tátra, turista-, hegymászó-, kerékpáros és szabadidÅtérkép, vydal Térkép Faragó Bt., Budapest, 2004', 5330 10016: 'J. Januszewski, G. GÅazek, W. Fedorowicz-Jackowski: Tatry i Podtatrze, Atlas Satelitarny, Geosystems Polska, Warszawa 2005', 5331 10017: 'WspóÅpracownicy Slowackiego TatraÅskiego Parku Narodowego: Juraj Ksiažek, Stanislav PavlarÄÃk, Ján Slivinský', 5332 10018: 'Kolektyw autorów: Tatra â PrÃroda, Wydawnictwo Baset, Praga 2010' 5333 }, 5334 5335 load(callback) { 5336 if (giga.futo.state === 0) { 5337 giga.futo.state = 1; 5338 fetch('futo/futo.json?s=2') 5339 .then(res => { 5340 if (!res.ok) throw new Error(`HTTP error ${res.status}`); 5341 return res.json(); 5342 }) 5343 .then(data => { 5344 giga.futo.state = 2; 5345 giga.futo.objects = data; 5346 callback(); 5347 }) 5348 .catch(err => { 5349 giga.futo.state = 0; 5350 console.error('Failed to load futo.json:', err); 5351 }); 5352 } else { 5353 callback(); 5354 } 5355 }, 5356 5357 parseNames(s) { 5358 const names = s.split('<BR>').map(e => giga.futo.parseString(e)); 5359 if (names.length === 1) names[0].main = true; 5360 return names; 5361 }, 5362 5363 getSource(i) { 5364 return giga.futo.sources[i]; 5365 }, 5366 5367 parseString(s) { 5368 const m = s.match(/(<B>)?([^<(]*)(\(([0-9]+)\))?(<\/B>)?/); 5369 return { 5370 txt: m[2].trim(), 5371 main: m[1] !== undefined, 5372 src: +(m[4] || 1), 5373 raw: s 5374 }; 5375 }, 5376 5377 replace(el) { 5378 const id = el.dataset.id; 5379 const name = el.dataset.name; 5380 giga.futo.load(() => { 5381 const o = id ? [giga.futo.findByID(id)] : giga.futo.findByName(name); 5382 const html = o.length > 0 5383 ? '<br><h2>SÅownik Endre Futó</h2>' + o.map(oo => giga.futo.toHTML(oo)).join('<br>') 5384 : '<br>nie znaleziono'; 5385 el.outerHTML = html; 5386 }); 5387 }, 5388 5389 findByID(id) { 5390 return giga.futo.objects[id] || {}; 5391 }, 5392 5393 findByName(name, lang = 'pl') { 5394 const ret = []; 5395 for (const obj of Object.values(giga.futo.objects)) { 5396 if (obj[lang]?.some(n => n.txt === name)) {
5397 ret.push(obj); 5398 } 5399 } 5400 return ret; 5401 }, 5402 5403 toHTML(o) { 5404 const langs = ['pl', 'sk', 'de', 'hu']; 5405 const html = langs 5406 .filter(lang => o[lang]) 5407 .map(lang => { 5408 const label = `<b style="text-transform:uppercase">${lang}</b> `; 5409 const names = o[lang] 5410 .map(t => { 5411 const title = giga.futo.sources[t.src] || ''; 5412 const cls = t.main ? 'futo-main' : ''; 5413 return `<span title="${title}" class="${cls}">${t.txt}</span>`; 5414 }) 5415 .join(' • '); 5416 return `<div>${label}${names}</div>`; 5417 }) 5418 .join(''); 5419 return `<div class="fitem"><b>ID</b> ${o.id}${html}</div>`; 5420 }, 5421 5422 get(name, lang) { 5423 const o = giga.futo.findByName(name, lang); 5424 return o.length > 0 ? o.map(oo => giga.futo.toHTML(oo)).join('<br>') : ''; 5425 } 5426 5427 }, 5428 5429 futo: { 5430 5431 state: 0, 5432 5433 objects: {}, 5434 5435 sources: { 5436 0: '', 5437 5438 // Wysokie 5439 1: 'Ivan BohuÅ¡: Od A po Z o názvoch Vysokých Tatier, TANAP - Tatranská Lomnica 1996', 5440 2: 'Komarnicki Gyula: A Magas-Tátra hegyvilága, Sport Budapest, 1985., wydanie II przepracowane Henrykom Bucsekom', 5441 3: 'Z. RadwaÅska-Paryska, W. H. Paryski: Wielka encyklopedia tatrzaÅskai, Wydawnictwo górskie â Poronin 1995.', 5442 4: 'Farkas Zsolt, Kunos Gábor: A Magas-Tátra szlovák-magyar nevei: ElÅtanulmány a Magas-Tátra gerinctérképéhez, niewydane studium, 1997.', 5443 5: 'Futó Endre: nowa nazwa wprowadzona po raz pierwszy w tej pracy', 5444 6: 'Józef Nyka: Tatry SÅowackie, Przewodnik, Wydanie II, Trawers, Latchorzew 1998; Józef Nyka: Tatry Polskie, Przewodnik, Wydanie X, Trawers, Latchorzew 1998', 5445 7: 'Alfred Grósz: Die Hohe Tatra, Arbeitgemeinschaft der Karpatendeutschen aus Slowakei, Stuttgart 1961', 5446 8: 'Arno PuÅ¡káš: Vysoké Tatry, horolezecký sprievodca, tom I-X, Vydavateľstvo Å port, Bratislava 1957-1989', 5447 9: 'Turistaság és Alpinizmus, 1910-1935', 5448 10: 'Szlovákia autóstérkép magyar településnevekkel, AGÃT Kft. TOP-O-GRÃF Térképészeti Iroda, Budapest 2000.', 5449 11: 'Szontagh Miklós: A Magas Tátra és hegyvidéke, Ãjtátrafüred 1895., wydanie autora', 5450 12: 'Neidenbach Ãkos: nowa nazwa wprowadzona po raz pierwszy w tej pracy lub kolekcja starych nazw z roku 1994', 5451 13: 'Komarnicki Gyula: A Magas-Tátra hegymászókalauza, Wydanie III, Késmárk-Budapest, 1926 Turistaság és Alpinizmus lap-, könyv- és térképkiadó RT', 5452 14: 'Komarnicki Gyula: Hohe Tatra, Késmárk 1918', 5453 15: 'Witold H. Paryski: Tatry Wysokie - Przewodnik Taternicki, tom I-XXIV, Sport i Turystyka, Warszawa, 1951-1988', 5454 16: 'Scheirich László: Emberek, sziklák, századok, Nap kiadó, Dunajská Streda 2001.', 5455 17: 'Klaus Gattinger: Die Hohe Tatra, ViViT s.r.o., Kieżmarok 2003.', 5456 18: 'WÅadysÅaw CywiÅski: Tatry, przewodnik szczegóÅowy, tom 1â19, Poronin 1994â2014', 5457 19: 'Andrzej Z. Górski: korekta jÄzykowa czÄÅci polskiej', 5458 20: 'Jan KieÅkowski: Tatry Wysokie, czÄÅÄ 1-2, Wydawnictwo EXPLO, Gliwice, 1997; ; czÄÅÄ 3, Antykwariat Górski Filar, Kielce 2013.', 5459 21: 'Wildner Dénes: A történelmi Magyarország egykori területeinek helynévtára, Verlag Ungarisches Institut, München 1996', 5460 22: 'Ivan BohuÅ¡: Rozprávanie o názvoch a o prvých výstupoch na tatranské vrcholy, Vydavateľstvo I&B, Tatranská Lomnica, 2003.', 5461 23: 'Kolbenheyer Károly, Kovács Pál: A Magas Tátra, Teschen, 1882.', 5462 24: 'Bácskai Gusztáv: korekta jÄzykowa czÄÅci wÄgierskiej', 5463 25: 'Alfred Grósz: Die Benennungen der Ãstlichen Tatra, niewydany rÄkopis, Kieżmarok', 5464 26: 'MiloÅ¡ JanoÅ¡ka: Sprievodca po Vysokých Tatrách, wydanie druge ponownie, wydal F. KlimeÅ¡ v Liptovskim ÅwiÄtym Mikulaszy, 1923.', 5465 27: 'MiloÅ¡ JanoÅ¡ka: Sprievodca po Tatrách, wydal F. KlimeÅ¡ v Liptovskim ÅwiÄtym Mikulaszy, 1911.', 5466 28: 'Adam Piechowski: GraÅ Tatr, Wydawnictwo TEXT, Warszawa 1992.', 5467 29: 'Nagy Ãrpád: korekta jÄzykowa czÄÅci wÄgierskiej', 5468 30: 'Marian JurÃk: leÅnik Tatranskiego Narodowego Parku', 5469 31: 'Karel Drož: Tatry, wydal F. KlimeÅ¡ v Rużomberku, 1897; Karel Drož: Život na Tatrách, Rokycany 1906; Karel Drož: Z naÅ¡ich Tater, Użhorod 1924', 5470 32: 'MaroÅ¡ BobovÄák, MaroÅ¡ Jacina: Vysoké Tatry â zachodnia czeÅÄ, przewodnik taternicki, Szczyrbskie Jezioro 2010; MaroÅ¡ BobovÄák, MaroÅ¡ Jacina: Vysoké Tatry â wschodnia czeÅÄ, przewodnik taternicki, Szczyrbskie Jezioro 2011', 5471 33: 'Pavol JackoviÄ: Slavkovský Å¡tÃt, horolezecký sprievodca, Poprad 2014;', 5472 34: 'Ivan BohuÅ¡ sen. i Ivan BohuÅ¡ jun.: PrÃbehy zemepisných názvov Vysokých Tatier, Wydawnictwo IB Vysoké Tatry s.r.o., Åomnica TatrzaÅska 2018', 5473 35: 'Jozef GurnÃk, Krywan, niewydane studium', 5474 5475 // Bielskie 5476 1001: 'WÅadysÅaw CywiÅski: Tatry, przewodnik szczegóÅowy, IV. i V. tom, Poronin 1997.', 5477 1002: 'Tadeusz Szczerba: Tatry Bielskie, Monografia, Przewodnik, Wydawnictwo Ryszard M. Remiszewski â RMR, Gliwice 1997.', 5478 1003: 'Z. RadwaÅska-Paryska, W. H. Paryski: Wielka encyklopedia tatrzaÅskai, Wydawnictwo górskie â Poronin 1995.', 5479 1004: 'Witold H. Paryski: Tatry Wysokie â Przewodnik Taternicki, 24. tom, Wydawnictwo Sport i Turystyka, Warszawa, 1951-1988.', 5480 1005: 'Futó Endre: nowa nazwa wprowadzona po raz pierwszy w tej pracy', 5481 1006: 'StanisÅaw Eljasz-Radzikowski: Tatry Bielskie, Pamietnik Towarzystwa TatrzaÅskiego, Kraków 1893 és 1894.', 5482 1007: 'Józef Nyka: Tatry SÅowackie, Przewodnik, Wydanie VI, Trawers, Latchorzew 1998.', 5483 1008: 'Klaus Gattinger: Die Hohe Tatra, ViViT s.r.o., Kieżmarok 2003.', 5484 1009: 'Alfred Grósz: Die Benennungen der Ãstlichen Tatra, niewydany rÄkopis, Kieżmarok', 5485 10010: 'Szontagh Miklós: A Magas Tátra és hegyvidéke, Ãjtátrafüred 1895., wydanie autora', 5486 10011: 'Kolbenheyer Károly, Kovács Pál: A Magas Tátra, Teschen, 1882.', 5487 10012: 'Hefty Gyula Andor és Vigyázó János: A Magas Tátra részletes kalauza, Turistaság és Alpinizmus kiadása, Budapest 1917.', 5488 10013: 'Ivan BohuÅ¡: Od A po Z o názvoch Vysokých Tatier, TANAP â Tatranská Lomnica 1996.', 5489 10014: 'Komarnicki Gyula: A Magas-Tátra hegyvilága, Sport Budapest, 1985., wydanie II przepracowane Henrykom Bucsekom', 5490 10015: 'Tatra Wysokie sÅowackie i polskie, mapa turystyczna 1:25000, Wydawnictwo
5490Kartograficzne Polkart, Warszawa-Zielona Góra-Zakopane, 2003/2004; Vysoké Tatry, turistická mapa 1:25000, Sygnatura, 6. vydanie, 2010; Magas-Tátra, turista-, hegymászó-, kerékpáros és szabadidÅtérkép, vydal Térkép Faragó Bt., Budapest, 2004', 5491 10016: 'J. Januszewski, G. GÅazek, W. Fedorowicz-Jackowski: Tatry i Podtatrze, Atlas Satelitarny, Geosystems Polska, Warszawa 2005', 5492 10017: 'WspóÅpracownicy Slowackiego TatraÅskiego Parku Narodowego: Juraj Ksiažek, Stanislav PavlarÄÃk, Ján Slivinský', 5493 10018: 'Kolektyw autorów: Tatra â PrÃroda, Wydawnictwo Baset, Praga 2010' 5494 5495 }, 5496 5497 load: function(callback) { 5498 if (giga.futo.state === 0) { 5499 giga.futo.state = 1; 5500 fetch('futo/futo.json?s=2') 5501 .then(res => { 5502 if (!res.ok) throw new Error(`HTTP error ${res.status}`); 5503 return res.json(); 5504 }) 5505 .then(data => { 5506 giga.futo.state = 2; 5507 giga.futo.objects = data; 5508 callback(); 5509 }) 5510 .catch(err => { 5511 giga.futo.state = 0; 5512 console.error('Failed to load futo.json:', err); 5513 }); 5514 } else { 5515 callback(); 5516 } 5517 }, 5518 5519 parseNames: function(s) { 5520 var ret = []; 5521 const names = s.split('<BR>'); 5522 names.forEach( function(e) { 5523 ret.push( giga.futo.parseString(e) ); 5524 }); 5525 if (ret.length==1) ret[0].main = true; 5526 return ret; 5527 }, 5528 5529 getSource: function(i) { 5530 return giga.futo.sources[i]; 5531 }, 5532 5533 parseString: function(s) { 5534 const m = s.match(/(<B>)?([^<\(]*)(\(([0-9]+)\))?(<\/B>)?/); 5535 return { 5536 txt: m[2].trim(), 5537 main: m[1]!==undefined, 5538 src: +(m[4] || 1), 5539 raw: s 5540 } 5541 }, 5542 5543 replace: function(el) { 5544 const $el = $(el); 5545 giga.futo.load( function() { 5546 let o = $el.data('id') ? [giga.futo.findByID($el.data('id'))] : giga.futo.findByName($el.data('name')); 5547 $el.replaceWith( o.length>0 ? ('<br><h2>SÅownik Endre Futó</h2>' + o.map( oo => giga.futo.toHTML(oo) ).join('<br>')) : '<br>nie znaleziono' ); 5548 }); 5549 }, 5550 5551 findByID: function(id) { 5552 return giga.futo.objects[id] || {}; 5553 }, 5554 5555 findByName: function(name, lang = 'pl') { 5556 var ret = [], found = false; 5557 Object.keys(giga.futo.objects).forEach(function(el) { 5558 found = false; 5559 giga.futo.objects[el][lang].forEach(function(n) { 5560 if (name==n.txt && !found) { 5561 ret.push(giga.futo.objects[el]); 5562 found = true; 5563 } 5564 }); 5565 }); 5566 return ret; 5567 }, 5568 5569 toHTML: function(o) { 5570 var html = ''; 5571 ['pl','sk','de','hu'].forEach( function(lang) { 5572 if (o[lang]) { 5573 let s = '<b style="text-transform:uppercase">'+lang+'</b> '; 5574 let z = o[lang].map( t => '<span title="' + (giga.futo.sources[t.src] || '') + '" class="'+(t.main?'futo-main':'')+'">' + t.txt + '</span>' ).join(' • '); 5575 html += '<div>' + s + z + '</div>'; 5576 } 5577 }); 5578 return '<div class="fitem"><b>ID</b> '+o.id+html+'</div>'; 5579 }, 5580 5581 get: function(name,lang) { 5582 const o = giga.futo.findByName(name,lang); 5583 return o.length>0 ? (o.map( oo => giga.futo.toHTML(oo) ).join('<br>')) : ''; 5584 } 5585 }, 5586 5587 elevationSources: { 5588 GUGIK: 1, 5589 ZBGIS: 2, 5590 SWISS: 3, 5591 CUZK: 4, 5592 OPEN: 5 5593 }, 5594 5595 fetchElevation: async function(lat,lon,source) { 5596 var url, response, t, h; 5597 var ret = { ok: false }; 5598 try { 5599 switch (source) { 5600 case giga.elevationSources.GUGIK: 5601 proj4.defs("EPSG:2180","+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); 5602 let pl1992 = proj4("EPSG:2180").forward([+lon,+lat]); 5603 url = `https://services.gugik.gov.pl/nmt/?request=GetHbyXY&x=${pl1992[1].toFixed(3)}&y=${pl1992[0].toFixed(3)}`; 5604 response = await fetch(url); 5605 if (response?.ok) { 5606 t = await response.text(); 5607 if (+t>=0) { 5608 ret.ok = true; 5609 ret.elevation = +t; 5610 } 5611 } 5612 break; 5613 case giga.elevationSources.ZBGIS: 5614 url = `https://zbgis.skgeodesy.sk/zbgis/rest/services/LLS_DMR5/MapServer/identify?f=json&geometryType=esriGeometryPoint&geometry=%7B%22y%22:${lat},%22x%22:${lon}%7D&sr=4326&mapExtent=0,0,0,0&imageDisplay=1,1,1&tolerance=0&layers=all:3&returnGeometry=false&returnZ=false&returnM=false`; 5615 response = await fetch(url); 5616 if (response?.ok) { 5617 t = await response.json(); 5618 if (t?.results.length>
56180 && t?.results[0].attributes['Stretch.Pixel Value']!='NoData') { 5619 ret.ok = true; 5620 ret.elevation = +t?.results[0].attributes['Stretch.Pixel Value']; 5621 ret.extra = t?.results[0].layerName; 5622 } 5623 } 5624 break; 5625 case giga.elevationSources.CUZK: 5626 proj4.defs("EPSG:5514","+proj=krovak +lat_0=49.5 +lon_0=24.83333333333333 +alpha=30.28813972222222 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=570.8,85.7,462.8,4.998,1.587,5.261,3.56 +units=m +no_defs"); 5627 let krovak = proj4("EPSG:5514").forward([+lon,+lat]); 5628 url = `https://ags.cuzk.cz/arcgis2/rest/services/dmr5g/ImageServer/identify?geometry={x:${krovak[0].toFixed(5)},y:${krovak[1].toFixed(5)}}&geometryType=esriGeometryPoint&returnGeometry=false&returnCatalogItems=false&f=pjson`; 5629 response = await fetch(url); 5630 if (response?.ok) { 5631 t = await response.json(); 5632 h = Number.parseFloat(t.value); 5633 if (!Number.isNaN(h)) { 5634 ret.ok = true; 5635 ret.elevation = h; 5636 } 5637 } 5638 break; 5639 case giga.elevationSources.SWISS: 5640 proj4.defs("EPSG:2056","+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs"); 5641 let swiss = proj4("EPSG:2056").forward([+lon,+lat]); 5642 url = `https://api3.geo.admin.ch/rest/services/height?sr=2056&easting=${Math.round(swiss[0])}&northing=${Math.round(swiss[1])}`; 5643 response = await fetch(url); 5644 if (response?.ok) { 5645 t = await response.json(); 5646 h = Number.parseFloat(t.height); 5647 if (!Number.isNaN(h)) { 5648 ret.ok = true; 5649 ret.elevation = h; 5650 } 5651 } 5652 break; 5653 case giga.elevationSources.OPEN: 5654 url = `https://api.openelevation.com/v1/elevation?lat=${lat}&lon=${lon}`; 5655 response = await fetch(url); 5656 if (response?.ok) { 5657 t = await response.json(); 5658 h = Number.parseFloat(t.elevation); 5659 if (!Number.isNaN(h)) { 5660 ret.ok = true; 5661 ret.elevation = h; 5662 } 5663 } 5664 break; 5665 } 5666 } catch (error) { 5667 ret.extra = 'Network error'; 5668 return ret; 5669 } 5670 return ret; 5671 }, 5672 5673 fetchGugikElevation: function(lat,lon) { 5674 proj4.defs("EPSG:2180","+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); 5675 const pl1992 = proj4("EPSG:2180").forward([+lon,+lat]); 5676 const url = 'https://services.gugik.gov.pl/nmt/?request=GetHbyXY&x=' + pl1992[1].toFixed(3) + '&y=' + pl1992[0].toFixed(3); 5677 return fetch(url).then(response => response?.ok ? response.text() : null); 5678 }, 5679 5680 fetchSwissElevation: function(lat,lon) { 5681 proj4.defs("EPSG:2056","+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs"); 5682 const swiss = proj4("EPSG:2056").forward([+lon,+lat]); 5683 const url = 'https://api3.geo.admin.ch/rest/services/height?sr=2056&easting=' + Math.round(swiss[0]) + '&northing=' + Math.round(swiss[1]); 5684 return fetch(url).then(response => response?.ok ? response.json() : null); 5685 }, 5686 5687 fetchZbgisElevationOLD: async function(lat,lon) { 5688 const url = 'https://zbgis.skgeodesy.sk/zbgis/rest/services/LLS_DMR5/MapServer/identify?f=json&geometryType=esriGeometryPoint&geometry=%7B%22y%22:'+lat+',%22x%22:'+lon+'%7D&sr=4326&mapExtent=0,0,0,0&imageDisplay=1,1,1&tolerance=0&layers=all:3&returnGeometry=false&returnZ=false&returnM=false'; 5689 return await fetch(url).then(response => response?.ok ? response.json() : null); 5690 }, 5691 5692 fetchZbgisElevation: async function(lat,lon) { 5693 const url = `https://zbgis.skgeodesy.sk/zbgis/rest/services/LLS_DMR5/MapServer/identify?f=json&geometryType=esriGeometryPoint&geometry=%7B%22y%22:${lat},%22x%22:${lon}%7D&sr=4326&mapExtent=0,0,0,0&imageDisplay=1,1,1&tolerance=0&layers=all:3&returnGeometry=false&returnZ=false&returnM=false`; 5694 try { 5695 let response = await fetch(url); 5696 let e = await response.json(); 5697 if (e?.results.length>0) { 5698 return e.results[0]; 5699 } else { 5700 return null; 5701 } 5702 } catch(e) { 5703 console.log('fetchZbgisElevation error! ',e); 5704 return null; 5705 } 5706 }, 5707 5708 fetchCuzkElevation: async function(lat,lon) { 5709 proj4.defs("EPSG:5514","+proj=krovak +lat_0=49.5 +lon_0=24.83333333333333 +alpha=30.28813972222222 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=570.8,85.7,462.8,4.998,1.587,5.261,3.56 +units=m +no_defs"); 5710 const krovak = proj4("EPSG:5514").forward([+lon,+lat]); 5711 const url = 'https://ags.cuzk.cz/arcgis2/rest/services/dmr5g/ImageServer/identify?geometry={x:' + krovak[0].toFixed(5) + ',y:' + krovak[1].toFixed(5) + '}&geometryType=esriGeometryPoint&returnGeometry=false&returnCatalogItems=false&f=pjson'; 5712 return await fetch(url).then(response => response.ok ? response.json() : null); 5713 }, 5714 5715 fetchOpenElevation: async function (lat, lon) { 5716 return await fetch(`https://api.openelevation.com/v1/elevation?lat=${lat}&lon=${lon}`) 5717 .then(response => response.json()) 5718 .then(data => data.elevation); 5719 }, 5720 5721 openNMTRefine: function(lat,lon,title) { 5722 const params = new URLSearchParams({ 5723 'title': title ?? '', 5724 'lat': lat, 5725 'lon': lon,
5726 'radius': 4, 5727 'step': 5 5728 }); 5729 window.open('nmt-refine.html?'+params.toString()) 5730 }, 5731 5732 openSwissRefine: function(lat,lon,title) { 5733 const params = new URLSearchParams({ 5734 'title': title ?? '', 5735 'lat': lat, 5736 'lon': lon, 5737 'radius': 4, 5738 'step': 5 5739 }); 5740 window.open('swiss-refine.html?'+params.toString()) 5741 }, 5742 5743 openZbgisRefine: function(lat,lon,title) { 5744 const params = new URLSearchParams({ 5745 'title': title ?? '', 5746 'lat': lat, 5747 'lon': lon, 5748 'radius': 4, 5749 'step': 0.00005 5750 }); 5751 window.open('zbgis-refine.html?'+params.toString()) 5752 }, 5753 5754 openCuzkRefine: function(lat,lon,title) { 5755 const params = new URLSearchParams({ 5756 'title': title ?? '', 5757 'lat': lat, 5758 'lon': lon, 5759 'radius': 4, 5760 'step': 5 5761 }); 5762 window.open('cuzk-refine.html?'+params.toString()) 5763 }, 5764 5765 isPointVerified: function(point) { 5766 return (point?.source=="lidar-cloud" || point?.source=="lidar-refine" || point?.source=="zbgis-nmt" || point?.source=="gugik-refine"); 5767 }, 5768 5769 clearCacheAndReload: () => { 5770 hotspots.clearPointsStorage(); 5771 giga.sw.clearCache(); 5772 location.reload(); 5773 }, 5774 5775 userAgentString: 'PanoramyEuCountryCheck/1.0 ([email protected])', 5776 5777 checkCountry: (lat,lon) => { 5778 5779 function pointInPolygon(lat, lon, polygon) { 5780 let inside = false;
5781 for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { 5782 const xi = polygon[i][0], yi = polygon[i][1]; 5783 const xj = polygon[j][0], yj = polygon[j][1]; 5784 const intersect = ((yi > lon) !== (yj > lon)) && (lat < (xj - xi) * (lon - yi) / (yj - yi) + xi); 5785 if (intersect) inside = !inside; 5786 } 5787 return inside; 5788 } 5789 5790 const countryPolygons = { 5791 "PL":[[50.31748237,17.47802196],[50.37503024,17.65502653],[50.28333618,17.8132116],[50.1044646,17.7939597],[50.15363417,17.8810871],[50.07605843,18.15571504],[49.99876691,18.41025839],[49.94073048,18.66185922],[49.80558271,18.68474424],[49.65594743,18.85444605],[49.51608214,19.03858965],[49.55640434,19.23528091],[49.69237024,19.44662835],[49.58197412,19.61275572],[49.50053166,19.80135472],[49.34160418,19.8772705],[49.26898337,20.02634588],[49.41870314,20.20942672],[49.4419247,20.4607864],[49.39357254,20.74845534],[49.44480981,20.95988778],[49.49339712,21.222384],[49.49241152,21.51465971],[49.44349108,21.79021735],[49.35385446,22.05365185],[49.26362101,22.31617941],[49.17989168,22.57105395],[49.37291932,22.56303876],[49.56593244,22.5550614],[49.72192319,22.71554967],[49.87181878,22.90348126],[50.02136862,23.09266687],[50.17056916,23.28311847],[50.31941681,23.47484813],[50.44836568,23.6854022],[50.5224467,23.9273658],[50.69899109,23.92141927],[50.85485719,23.83993417],[50.97461676,23.87047238],[51.13186511,23.6918334],[51.29047544,23.52018601],[51.48335399,23.52007044],[51.6762232,23.52006795],[51.86908362,23.52017999],[52.05592791,23.50136684],[52.16756666,23.24453088],[52.27866118,22.98647096],[52.40876485,23.20486728],[52.53896766,23.43902114],[52.66867824,23.67463568],[52.83024899,23.79409882],[53.02121758,23.77132113],[53.20911685,23.69937544],[53.39697892,23.62691503],[53.58480384,23.5539314],[53.77259165,23.48041588],[53.9603424,23.40635961],[54.13043104,23.28245447],[54.25474964,23.03033541],[54.31783396,22.73518819],[54.32869663,22.40438962],[54.33867079,22.07346237],[54.34775601,21.7424195],[54.35595191,21.41127411],[54.36325818,21.08003932],[54.36967455,20.7487283],[54.37520083,20.41735423],[54.37983686,20.0859303],[54.38358255,19.75446974],[54.35661282,19.42887305],[54.30928895,19.10773198],[54.29644332,18.7933711],[54.37989703,18.4950765],[54.56240949,18.401485],[54.74891632,18.31555571],[54.75598213,17.99602134],[54.70674882,17.67509674],[54.64355148,17.35956324],[54.57954719,17.0450323],[54.51474036,16.73150982],[54.38625281,16.49753909],[54.23334424,16.29502893],[54.15841738,16.00294106],[54.10413197,15.68686107],[54.04903061,15.37162573],[53.9931171,15.05724241],[53.93639524,14.74371831],[53.82815385,14.74894005],[53.63574949,14.71793277],[53.50407171,14.63220044],[53.49958383,14.46769075],[53.31143637,14.54150543],[53.13219601,14.47869551],[52.95785639,14.34002419],[52.80707783,14.38908602],[52.68033902,14.62959527],[52.53203817,14.74534514],[52.34907242,14.69694724],[52.17165734,14.82198244],[51.99324055,14.83286256],[51.81371665,14.7169306],[51.64112656,14.84926129],[51.46861895,14.98932043],[51.29594567,15.12836163],[51.1131154,15.05873856],[51.11603954,15.18144962],[50.94778777,15.33265196],[50.83416782,15.54
5791051633],[50.81215336,15.83727832],[50.68565205,16.01993525],[50.71539223,16.31699667],[50.6013801,16.52768685],[50.48273157,16.45804733],[50.35099282,16.43614457],[50.21006297,16.643086],[50.25264002,16.87887469],[50.43187428,16.7736665],[50.48654968,16.97325704],[50.39134346,17.23714086],[50.31748237,17.47802196],[50.31748237,17.47802196]], 5792 "SK":[[47.83167303,18.66814999],[47.8967594,18.69741245],[47.97825894,18.65406957],[48.04287784,18.73252385],[48.09363965,18.82971467],[48.10388166,18.95793113],[48.11397307,19.0862038],[48.12391376,19.21453202],[48.13370363,19.34291514],[48.17784682,19.44508636],[48.24125985,19.53271924],[48.28563582,19.62615058],[48.24858339,19.74345766],[48.21140802,19.86059955],[48.21011611,19.97547679],[48.25280038,20.08788066],[48.29536564,20.20048229],[48.35368758,20.28790527],[48.43128194,20.34462149],[48.50884139,20.40152746],[48.58272545,20.46281826],[48.59855177,20.59102328],[48.61422785,20.71931516],[48.61529557,20.84566606],[48.5838276,20.96740859],[48.5522278,21.08900497],[48.56978103,21.21180253],[48.5979395,21.33501392],[48.61851798,21.45552861],[48.55465075,21.54376791],[48.49071531,21.63179482],[48.42671191,21.71961019],[48.40631719,21.83017454],[48.41973533,21.95859634],[48.45333751,22.05485495],[48.53288724,22.10499323],[48.61240811,22.15530841],[48.69190002,22.20580149],[48.77136284,22.25647346],[48.85079648,22.30732533],[48.93020081,22.35835812],[49.00957572,22.40957285],[49.06048451,22.35807137],[49.09119631,22.23591476],[49.12205574,22.1125276],[49.14858693,21.99325955],[49.22172094,21.92701223],[49.29194539,21.8631214],[49.32492621,21.74737484],[49.36027642,21.62627681],[49.37140484,21.49766229],[49.36970415,21.36508342],[49.36807306,21.24879141],[49.36606895,21.11624322],[49.30014143,21.03408928],[49.2314089,20.95433371],[49.25471355,20.85410933],[49.30600615,20.74741032],[49.34537598,20.63528761],[49.34227966,20.5028835],[49.33902609,20.37049885],[49.28416379,20.27784612],[49.23823051,20.17846714],[49.15799711,20.1373603],[49.11436946,20.04706864],[49.15985839,19.93632408],[49.13556515,19.82303727],[49.14489285,19.69184206],[49.22208243,19.67939931],[49.30286282,19.69811947],[49.34734617,19.63380453],[49.37429481,19.53815446],[49.4488072,19.48770461],[49.52011117,19.43430228],[49.47100917,19.33088268],[49.4160529,19.27278221],[49.35522602,19.17887643],[49.3457454,19.06180826],[49.33493093,18.93365958],[49.39330916,18.87732465],[49.4561755,18.83273234],[49.4419781,18.70659368],[49.43059842,18.57815837],[49.37311072,18.47936731],[49.31553208,18.38081749],[49.25786283,18.28250819],[49.19241175,18.20037864],[49.10743184,18.18469077],[49.02845815,18.14860069],[48.97128198,18.05108616],[48.93273258,17.96631203],[48.86870943,17.8869975],[48.83398143,17.77838827],[48.79494014,17.66165374],[48.75742303,17.54470554],[48.77196636,17.42577407],[48.78355956,17.30160629],[48.80100379,17.18415143],[48.72115173,17.13573005],[48.64048892,17.08902228],[48.55980206,17.04247545],[48.47909123,16.99608863],[48.39835653,16.94986088],[48.31884985,16.96542679],[48.24009451,17.0191961],[48.16131205,17.07280802],[48.0828998,17.12673605],[48.06550374,17.25323371],[48.0463216,17.37845151],[47.99000796,17.47652287],[47.93360792,17.57438481],[47.87712178,17.67203794],[47.82054984,17.76948285],[47.82196399,17.89749249],[47.82395959,18.02590313],[47.82580427,18.15432759],[47.82749798,18.2827652],[47.8290407,18.41121526],[47.83043239,18.53967709],[47.83167303,18.66814999],[47.83167303,18.66814999]], 5793 "CZ":[[48.70758686,14.44183779],[48.66208879,14.59009478],[48.72017759,14.67692584],[48.82011924,14.73923992],[48.83245264,14.8747384],[48.93902653,14.9023473],[49.04558817,14.9300874],[49.05744769,15.0519494],[49.0309939,15.21152378],[49.00431367,15.37093139],[48.9774074,15.53017123],[48.9502755,15.68924234],[48.92291836,15.84814374],[48.8602801,15.9814906],[48.79946088,16.11484813],[48.81191794,16.2774786],[48.82413644,16.44019521],[48.83611613,16.60299661],[48.80677144,16.7576838],[48.76722843,16.91017412],[48.79284968,16.99254791],[48.88569227,17.06761387],[48.89380777,17.23118633],[48.88398778,17.38625882],[48.87143297,17.53253198],[48.90987194,17.6846625],[48.97756657,17.81243692],[49.04525126,17.94044363],[49.12876518,18.023134],[49.23324015,18.06501078],[49.32265973,18.14515647],[49.3653497,18.29629268],[49.4534246,18.32981714],[49.49320489,18.45329712],[49.54048316,18.57984176],[49.5
57935260615,18.73075393],[49.62875354,18.66945238],[49.72390719,18.59511805],[49.82253273,18.5266336],[49.86602276,18.39513014],[49.8618917,18.2476014],[49.93096245,18.1247622],[49.97312631,17.98467194],[49.91138968,17.84722004],[49.97032936,17.72621815],[50.04887832,17.62699778],[50.11778791,17.55068528],[50.21467165,17.6040324],[50.20651541,17.54323108],[50.18963489,17.3917725],[50.24703278,17.25377774],[50.31080315,17.13322207],[50.35366844,16.99785654],[50.26322379,17.07897824],[50.17713459,17.07211805],[50.14410618,16.924291],[50.08574603,16.79734532],[50.02952748,16.65558011],[50.09820149,16.53804868],[50.18644437,16.4504751],[50.26671191,16.34657381],[50.31884691,16.21661309],[50.39566896,16.11598907],[50.48113002,16.19091826],[50.55143033,16.30840132],[50.57726688,16.2268799],[50.56588486,16.07702586],[50.55293845,15.92452857],[50.62070684,15.84662298],[50.69336122,15.72751714],[50.71621489,15.56297048],[50.72294765,15.41315846],[50.75553704,15.27678511],[50.84418821,15.19901363],[50.92444658,15.13015444],[50.90928221,15.06306249],[50.80432516,15.07620375],[50.80261777,14.91257483],[50.79642323,14.74222017],[50.78997691,14.57191578],[50.88098628,14.49619041],[50.98016975,14.42815419],[50.89990392,14.42678622],[50.82832497,14.37016886],[50.79340632,14.20867439],[50.75826027,14.04742646],[50.72288741,13.88642585],[50.68728834,13.72567329],[50.64525146,13.5705181],[50.57872846,13.4366162],[50.51204695,13.30309831],[50.44520767,13.16996323],[50.37821138,13.03720976],[50.34785126,12.88306392],[50.34206983,12.71434776],[50.32871712,12.55263549],[50.23568697,12.46671304],[50.14258878,12.38113157],[50.05710147,12.3587334],[49.99038289,12.49083784],[49.92351131,12.6225782],[49.83125068,12.55571077],[49.73693922,12.48314502],[49.64173581,12.56225398],[49.54647541,12.64105805],[49.45869605,12.73148599],[49.40457064,12.87506101],[49.35026291,13.01832105],[49.27445303,13.13379038],[49.19154016,13.23990055],[49.10852646,13.34565887],[49.02541244,13.45106722],[49.00079325,13.60435305],[48.93760601,13.7258874],[48.85430619,13.83062175],[48.77090763,13.93501202],[48.68741083,14.03906003],[48.63674638,14.17333526],[48.63273928,14.32412509],[48.70758686,14.44183779],[48.70758686,14.44183779]] 5794 }; 5795 5796 let res = {}; 5797 Object.entries(countryPolygons).some( ([country, polygon]) => { 5798 if (pointInPolygon(lat, lon, polygon)) { 5799 res = {countryCode: country}; 5800 return true; 5801 } else { 5802 return false; 5803 } 5804 }); 5805 return res; 5806 }, 5807 5808 checkCountryOSM: async function (lat, lon) { 5809 const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=3&addressdetails=1`; 5810 5811 const res = await fetch(url, { 5812 headers: { 5813 "User-Agent": giga.userAgentString, 5814 } 5815 }); 5816 5817 if (!res.ok) { 5818 throw new Error(`Nominatim error: ${res.status}`); 5819 } 5820 5821 const data = await res.json(); 5822 5823 // Nominatim returns lowercase ISO2 codes (e.g., "fr", "us") 5824 const code = data.address?.country_code; 5825 5826 if (!code) { 5827 return []; // ocean, Antarctica, or no match 5828 } 5829 5830 return [code.toUpperCase()]; 5831 }, 5832 5833 peakFinder: { 5834 panel: null, 5835 window: null, 5836 eventID: null, 5837 open: function() { 5838 window.addEventListener('unload', giga.peakFinder.close); 5839 window.addEventListener('pagehide', giga.peakFinder.close); 5840 giga.peakFinder.window = window.open('peakfinder.html','peakfinder-window'); 5841 }, 5842 setCurrentView: function() { 5843 const scalingFactor = 1.0; 5844 if (giga.peakFinder.panel) { 5845 if (giga.peakFinder.eventID) window.clearTimeout(giga.peakFinder.eventID); 5846 giga.peakFinder.eventID = window.setTimeout(function() { 5847 const currentView = giga.getCurrentViewParameters(); 5848 const currentVerticalAngle = giga.getCurrentVerticalAngle() * scalingFactor; 5849 giga.peakFinder.panel.addEventListener('viewpointjourney finished', async function(args) { 5850 console.log(`viewpoint ready`, args); 5851 const pfAltitude = +args.elev, elevation = +k.info.elevation; 5852 const eleDifference = elevation - pfAltitude; 5853 console.log('eleDifference', eleDifference); 5854 if (Math.abs(eleDifference)>5) { 5855 console.log('setting eleDifference'); 5856 giga.peakFinder.panel.elevationOffset(eleDifference); 5857 } 5858 }, { once: true }); 5859 giga.peakFinder.panel.azimut(currentView.center); 5860 giga.peakFinder.panel.fieldofview(currentView.range); 5861 window.setTimeout(function() { 5862 //console.log('currentVerticalAngle: ',currentVerticalAngle); 5863 giga.peakFinder.panel.altitude(currentVerticalAngle); 5864 }, 0); 5865 }, 0); 5866 } 5867 }, 5868 updateView: function() { 5869 if (giga.peakFinder.panel) giga.peakFinder.setCurrentView(); 5870 }, 5871 ready: function(panelReference) { 5872 console.log('peakfinder signaled ready'); 5873 const info = k.info, vRange = info.range || 250; 5874 giga.peakFinder.panel = panelReference; 5875 giga.peakFinder.panel.loadViewpoint(+info.lat, +info.lon, info.title); 5876 giga.peakFinder.panel.settings.visibilityRange(vRange*1000); 5877 giga.peakFinder.panel.astro.currentDateTime(+info.date.substr(0,4), +info.date.substr(5,2), +info.date.substr(8,2), +info.time.substr(0,2), +info.time.substr(3,2)); 5878 window.setTimeout(function() { 5879 if (giga.peakFinder.window) giga.peakFinder.window.document.title = giga.peakFinder.panel.viewpoint.name(); 5880 giga.peakFinder.setCurrentView(); 5881 }, 600); 5882 }, 5883 finish: function() { 5884 giga.peakFinder.window = giga.peakFinder.panel = null; 5885 window.removeEventListener('unload', giga.peakFinder.close); 5886 window.removeEventListener('pagehide', giga.peakFinder.close); 5887 }, 5888 close: function() { 5889 try { 5890 giga.peakFinder.window.close(); 5891 } catch { 5892 } finally { 5893 giga.peakFinder.finish(); 5894 } 5895 }, 5896 updateOptimizer: function() { 5897 console.log('updating optimizer'); 5898 const info = k.info; 5899 const v = document.getElementById('vertical-angle-optimizer'); 5900 v.querySelector('.vertical-angle-top').textContent = `${(+info.azimuthtop).toFixed(1)}°`; 5901 v.querySelector('.vertical-angle-bottom').textContent = `${(+info.azimuthbottom).toFixed(1)}°`; 5902 giga.peakFinder.updateView(); 5903 }, 5904 showOptimizer: function() { 5905 const v = $('#vertical-angle-optimizer'); 5906 const step = 0.1; 5907 v.find('.top-down').on('click', () => { k.info.azimuthtop = Number((+k.info.azimuthtop+0.1).toFixed(2)); giga.peakFinder.updateOptimizer(); }); 5908 v.find('.bottom-up').on('click', () => { k.info.azimuthbottom = Number((+k.info.azimuthbottom-0.1).toFixed(2)); giga.peakFinder.updateOptimizer(); }); 5909 v.find('.top-up').on('click', () => { k.info.azimuthtop = Number((+k.info.azimuthtop-0.1).toFixed(2)); giga.peakFinder.updateOptimizer(); }); 5910 v.find('.bottom-down').on('click', () => { k.info.azimuthbottom = Number((+k.info.azimuthbottom+0.1).toFixed(2)); giga.peakFinder.updateOptimizer(); }); 5911 giga.peakFinder.updateOptimizer(); 5912 v.show(); 5913 }, 5914 hideOptimizer: function() { 5915 const v = $('#vertical-angle-optimizer'); 5916 v.find('.top-up').off('click'); 5917 v.find('.top-down').off('click'); 5918 v.find('.bottom-up').off('click'); 5919 v.find('.bottom-down').off('click'); 5920 $('#vertical-angle-optimizer').hide(); 5921 } 5922 }, 5923 5924 overpass: { 5925 buildQuery: function(point,around) { 5926 // https://overpass-api.de/api/interpreter?data=%5Bout%3Ajson%5D%5Btimeout%3A60%5D%3B%0A%28%2
59260node%5Bname%3D%22Mo%C5%A1n%C3%ADk%22%5D%5Bnatural%3D%22peak%22%5D%3B%20%29%3B%0Aout%20center%3B 5927 // new: 5928 // https://overpass-api.de/api/interpreter?data=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B%0A(%20node%5Bname%3D%22Mo%C5%A1n%C3%ADk%22%5D%5Bnatural%3D%22peak%22%5D%3B)%3B%0Aout%20center%3B 5929 const server = 'https://overpass-api.de/api/interpreter?data='; 5930 var kind = 'peak', aroundPart = ''; 5931 var q = '[out:json][timeout:90]; \n'; 5932 if (around===true) around = '(around:100.00,'+point.lat+','+point.lon+')'; 5933 //q += '( node'+aroundPart+'[name="' + point.title + '"][natural="' + kind + '"];); \n'; 5934 q += '( node'+aroundPart+'[~"^name(:.*)?$"~"^' + point.title + '$"][natural="' + kind + '"];); \n'; 5935 q += 'out center;'; 5936 const url = server + fixedEncodeURIComponent(q); 5937 return url; 5938 }, 5939 5940 JSONtoHTML: function(o) { 5941 var ret = ''; 5942 o.elements.forEach( e => { 5943 ret += `<div><b><big>${e.tags.name}</big></b><br><b>coords:</b> ${e.lat.toFixed(5)}, ${e.lon.toFixed(5)}</div>`; 5944 for (const t in e.tags) { 5945 ret += `<div><b>${t}</b>: ${e.tags[t]}</div>`; 5946 } 5947 ret += '<br>'; 5948 } ); 5949 return ret; 5950 }, 5951 5952 fetchNearby: async function(lat, lon, radiusInMeters=100, kind='peak') { 5953 const query = `[out:json][timeout:30]; 5954 (node["natural"="${kind}"](around:${radiusInMeters}, ${lat}, ${lon}); 5955 ); 5956 out body; 5957 >; 5958 out skel qt;`; 5959 console.log(query); 5960 const url = `https://overpass-api.de/api/interpreter?data=${encodeURIComponent(query)}`; 5961 try { 5962 const response = await fetch(url); 5963 const data = await response.json(); 5964 const mountainData = data.elements.map(element => ({ 5965 name: element.tags?.name || '???', 5966 lat: element.lat, 5967 lon: element.lon, 5968 elevation: element.tags?.ele || '' 5969 })); 5970 return mountainData; 5971 } catch (error) { 5972 console.error('Error fetching mountain data:', error); 5973 return []; 5974 } 5975 }, 5976 5977 getFromBounds: async function(lat1, lon1, lat2, lon2, kinds = [['natural', 'peak'], ['natural', 'saddle']]) { 5978 const nodes = kinds.length>0 ? 5979 kinds.map( pair => `node["${pair[0]}"="${pair[1]}"]["name"](${lat1},${lon1},${lat2},${lon2});` ).join("\n") : 5980 `node(${lat1},${lon1},${lat2},${lon2});;` 5981 ; 5982 const query = `[out:json][timeout:30]; 5983 ( 5984 ${nodes} 5985 ); 5986 out body; 5987 >; 5988 out skel qt;`; 5989 //console.log(query); 5990 const servers = ['https://overpass.kumi.systems','https://overpass-api.de']; 5991 const url = `${servers[Math.floor(Math.random() * servers.length)]}/api/interpreter?data=${encodeURIComponent(query)}`; 5992 try { 5993 const response = await fetch(url); 5994 if (!response.ok) { 5995 if (response.status === 504) { 5996 showScreenMessage('Gateway timeout, spróbuj później', { severity: 'error', duration: 1100, category: 'gather-objects-error'}); 5997 } else { 5998 showScreenMessage('BÅÄ d komunikacji (${response.status}), spróbuj później', { severity: 'error', duration: 1100, category: 'gather-objects-error'}); 5999 } 6000 return []; 6001 } 6002 const data = await response.json(); 6003 const mountainData = data.elements.map(element => { 6004 const ret = { 6005 name: element.tags?.name || '???', 6006 lat: element.lat, 6007 lon: element.lon, 6008 kind: element.tags?.natural, 6009 country: '', 6010 ele: element.tags?.ele ? Math.round(element.tags?.ele) : 0 6011 }; 6012 if (element.tags?.wikipedia) { 6013 const wiki = parseWikiLangString(element.tags.wikipedia); 6014 ret.wiki = wiki.text; 6015 if (wiki.lang && wiki.lang!=='pl') ret.wikilang = wiki.lang; 6016 } 6017 return ret; 6018 }); 6019 showScreenMessage('Sprawdzanie kraju', { duration: 5000, category: 'gather-objects'}); 6020 try { 6021 const countries = await giga.countryCheck.batch(mountainData.map(element => [element.lat, element.lon])); 6022 mountainData.forEach((element, i) => { 6023 element.country = countries[i].join(' '); 6024 }); 6025 } catch(e) { 6026 } 6027 return mountainData; 6028 } catch (error) { 6029 showScreenMessage('WystÄ piÅ problem', { severity: 'error', duration: 1100, category: 'gather-objects'}); 6030 console.error('Error fetching mountain data:', error); 6031 return []; 6032 } 6033 } 6034 }, 6035 6036 countryCheck: { 6037 // ------------------------- 6038 // Public API 6039 // ------------------------- 6040 6041 async single(lat, lon) { 6042 const providers = [ 6043 this._bigDataCloudLookup.bind(this), 6044 this._nominatimLookup.bind(this) 6045 ]; 6046 6047 for (const provider of providers) { 6048 try { 6049 const result = await provider(lat, lon); 6050 if (result.length > 0) { 6051 return result; 6052 } 6053 } catch (err) { 6054 // continue to next provider 6055 } 6056 } 6057 6058 return []; 6059 }, 6060 6061 async batch(coords, { parallel = false } = {}) { 6062 if (parallel) { 6063 // Run all lookups concurrently 6064 const tasks = coords.map(([lat, lon]) => this.single(lat, lon)); 6065 return Promise.all(tasks); 6066 } 6067 6068 // Run lookups sequentially (safer for rate limits) 6069 const results = []; 6070 for (const [lat, lon] of coords) { 6071 const r = await this.single(lat, lon); 6072 results.push(r); 6073 } 6074 return results; 6075 }, 6076 6077 // ------------------------- 6078 // Providers 6079 // ------------------------- 6080 6081 async _nominatimLookup(lat, lon) { 6082 const url = 6083 `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=3&addressdetails=1`; 6084 6085 const res = await fetch(url, { 6086 headers: { 6087 "User-Agent": giga.userAgentString 6088 } 6089 }); 6090 6091 if (!res.ok) throw new Error("Nominatim failed"); 6092 6093 const data = await res.json(); 6094 const code = data.address?.country_code; 6095 6096 return code ? [code.toUpperCase()] : []; 6097 }, 6098 6099 async _bigDataCloudLookup(lat, lon) { 6100 const url =
6101 `https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lon}&localityLanguage=en`; 6102 6103 const res = await fetch(url); 6104 if (!res.ok) throw new Error("BigDataCloud failed"); 6105 6106 const data = await res.json(); 6107 const code = data.countryCode; 6108 6109 return code ? [code.toUpperCase()] : []; 6110 } 6111 }, 6112 6113 mapycom: { 6114 APIKEY: 'sdn07WziDRnbXIEVJKzaKTRQtO0jYLxhklXggwezxwU', 6115 6116 objectsLayer: null, 6117 markerLayer: null, 6118 lineLayer: null, 6119 starLayer: null, 6120 contextMenuMarkerLayer: null, 6121 layerControl: null, 6122 recentLayers: [], 6123 6124 resetMap: function() { 6125 if (giga.mapycom.mapContainer) { 6126 giga.mapycom.mapContainer.remove(); 6127 giga.mapycom.mapContainer = null; 6128 } 6129 if (giga.mapycom.map) { 6130 giga.mapycom.map = null; 6131 } 6132 }, 6133 6134 isShown: () => { 6135 return giga.mapycom.mapContainer && giga.mapycom.mapContainer.style.display == 'block'; 6136 }, 6137 6138 toggle: () => { 6139 if (!giga.mapycom.mapContainer) { 6140 giga.mapycom.showMap(+k.info.lat,+k.info.lon); 6141 } else { 6142 if (giga.mapycom.mapContainer.style.display=='none') { 6143 giga.mapycom.mapContainer.style.display = 'block'; 6144 } else { 6145 giga.mapycom.mapContainer.style.display = 'none'; 6146 } 6147 } 6148 }, 6149 6150 showMap: function (lat, lon, name='', opts={}) { 6151 console.log('showMap', lat, lon, name, opts); 6152 const zoom = opts?.zoom ?? 14; 6153 const center = [lat, lon]; 6154 if (!giga.mapycom.mapContainer) { 6155 giga.mapycom.mapContainer = document.createElement("div"); 6156 giga.mapycom.mapContainer.setAttribute('id','mapycom-map'); 6157 giga.mapycom.mapContainer.classList.add('popup','draggable','resizable'); 6158 6159 const close = document.createElement("div"); 6160 close.textContent = 'X'; 6161 close.classList = 'close'; 6162 giga.mapycom.mapContainer.appendChild(close); 6163 6164 document.body.appendChild(giga.mapycom.mapContainer); 6165 $("#mapycom-map").show().draggable({ 6166 cursor: 'move', 6167 handle: '.close', 6168 scroll: false 6169 }); 6170 6171 L.Control.Layers.include({ 6172 getOverlays: function() { 6173 var control, layers; 6174 layers = {}; 6175 control = this; 6176 control._layers.forEach(function(obj) { 6177 var layerName; 6178 if (obj.overlay) { 6179 layerName = obj.name; 6180 return layers[layerName] = control._map.hasLayer(obj.layer); 6181 } 6182 }); 6183 return layers; 6184 }, 6185 getCurrentLayer: function() { 6186 const control = this; 6187 return control._layers.find( obj => !obj.overlay && control._map.hasLayer(obj.layer)); 6188 } 6189 }); 6190 6191 function getRadius() { 6192 const bounds = giga.mapycom.map.getBounds();
6193 const sw = bounds.getSouthWest(); 6194 const ne = bounds.getNorthEast(); 6195 const width = sw.distanceTo(L.latLng(sw.lat, ne.lng)); 6196 const height = sw.distanceTo(L.latLng(ne.lat, sw.lng)); 6197 const radius = Math.round(0.35 * height); 6198 return radius; 6199 } 6200 6201 function showMouseOverRectangle(e) { 6202 const radius = getRadius(); 6203 const bounds = e.latlng.toBounds(radius * 2); 6204 radiusRectangle = L.rectangle(bounds, { 6205 stroke: false, 6206 fillColor: "#ff7800", 6207 fillOpacity: 0.3 6208 }); 6209 radiusRectangle.addTo(giga.mapycom.map); 6210 } 6211 6212 function hideMouseOverRectangle(e) { 6213 giga.mapycom.map.removeLayer(radiusRectangle); 6214 } 6215 6216 async function gatherObjects(e, forceAll=false) { 6217 6218 let country = giga.checkCountry(+e.latlng.lat, +e.latlng.lng); 6219 6220 if (!country?.countryCode) country = await giga.getCountryFromCoordinates(+e.latlng.lat, +e.latlng.lng); 6221 6222 let data = []; 6223 if (country?.countryCode=='PL' && !forceAll) { 6224 showScreenMessage(`Polska - dane z GUGiK`, {duration: 10000, category: 'gather-objects'}); 6225 6226 const bounds = e.latlng.toBounds(getRadius() * 2); 6227 data = await giga.getGugikObjectsFromBounds(bounds.getSouth(), bounds.getWest(), bounds.getNorth(), bounds.getEast()); 6228 6229 data.forEach( el => { 6230 el.ele = 0; 6231 el.country = 'PL'; 6232 el.kind = ''; 6233 el.source = 'gugik'; 6234 }); 6235 6236 } else if (country?.countryCode=='SK' && !forceAll) { 6237 6238 showScreenMessage(`SÅowacja - dane z rejestru ZBGIS`, {duration: 10000, category: 'gather-objects'}); 6239 const response = await fetch('zbgis-find.html?'+makeParams({ 6240 lat: +e.latlng.lat, 6241 lon: +e.latlng.lng, 6242 radius: getRadius() 6243 })); 6244 data = await response.json(); 6245 6246 proj4.defs("EPSG:5514","+proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=485,169.5,483.8,7.786,4.398,4.103,0 +units=m +no_defs +type=crs"); 6247 // add wgs coords to results 6248 data.forEach( el => { 6249 const [lon, lat] = proj4("EPSG:5514").inverse([+el.xcoord,+el.ycoord]); 6250 el.lat = lat; 6251 el.lon = lon; 6252 el.country = 'SK'; 6253 el.source = 'zbgis'; 6254 }); 6255 6256 } else { 6257 6258 showScreenMessage(`${country?.countryCode || 'nieznany kraj'} - Overpass`, {duration: 30000, category: 'gather-objects'}); 6259 const bounds = e.latlng.toBounds(getRadius() * 2); 6260 // ALL objects 6261 data = await giga.overpass.getFromBounds(bounds.getSouth(), bounds.getWest(), bounds.getNorth(), bounds.getEast()); 6262 data.forEach( el => { 6263 el.source = 'overpass'; 6264 }); 6265 //console.log(data); 6266 6267 } 6268 6269 const makeXMLfromRow = function (el) { 6270 let nameX = el.name, names = '', wiki = '', wikilang = ''; 6271 if (hotspots.points.has(nameX)) { 6272 if (el.ele>0) { 6273 nameX += ` (${el.ele} m)`; 6274 } 6275 if (hotspots.points.has(nameX)) { 6276 nameX = el.name + '*'; 6277 } 6278 } 6279 if (Array.isArray(el.names)) { 6280 names = el.names.map(e => `<name>${e}</name>`).join(''); 6281 } else { 6282 names = `<name>${el.name}</name>`; 6283 } 6284 if (el.wiki && el.wikilang) wikilang = ` wikilang="${el.wikilang}"`; 6285 6286 return `<point id="${nameX}" wiki="${el.wiki || ''}"${wikilang} lat="${el.lat.toFixed(5)}" lon="${el.lon.toFixed(5)}" source="${el.source}" elevation="${el.ele>0 ? el.ele : ''}" country="${el.country}" kind="${el.kind}">${names}<range></range></point>`; 6287 } 6288 6289 const xmlStrings = [], rawNames = []; 6290 data.forEach( el => { 6291 const xmlString = makeXMLfromRow(el); 6292 rawNames.push(el.name); 6293 xmlStrings.push(xmlString); 6294 //console.log(xmlString); 6295 giga.mapycom.addPoint(el.lat, el.lon, el.name); 6296 }); 6297 6298 if (xmlStrings.length==0) { 6299 showScreenMessage('Nie znaleziono żadnych obiektów', {category: 'gather-objects'}); 6300 } else { 6301 showScreenMessage(`Skopiowano ${xmlStrings.length} ${odmien(xmlStrings.length,'obiekt','obiekty','obiektów')}: ${rawNames.join(', ')}`, {duration: 2000, category:'gather-objects'}); 6302 giga.clipboard.copy(xmlStrings.join("\n")); 6303 } 6304 } 6305 6306 let radiusRectangle; 6307 6308 const menuOptions = { 6309 contextmenu: true, 6310 contextmenuAnchor: [0, 0], 6311 contextmenuItems: [ 6312 { 6313 text: '***' 6314 }, 6315 '-', 6316 { 6317 text: 'WiÄcej o tych wspóÅrzÄdnych', 6318 callback: e => { 6319 window.open(`/giga/coord.html?lat=${e.latlng.lat.toFixed(6)}&lon=${e.latlng.lng.toFixed(6)}`); 6320 } 6321 }, 6322 { 6323 text: 'Otwórz w Google Maps', 6324 callback: e => { 6325 window.open(`https://www.google.com/maps?ll=${e.latlng.lat},${e.latlng.lng}&z=${giga.mapycom.map.getZoom()}`); 6326 } 6327 }, 6328 { 6329 text: 'Otwórz bieÅ¼Ä cÄ mapÄ', 6330 callback: e => { 6331 if (typeof giga.mapycom.layerControl.getCurrentLayer().layer.options.getURL == 'function') { 6332 window.open(giga.mapycom.layerControl.getCurrentLayer().layer.options.getURL(e.latlng.lat, e.latlng.lng, giga.mapycom.map.getZoom())); 6333 } 6334 } 6335 }, 6336 { 6337 text: 'Pokaż wysokoÅÄ z mapy.com', 6338 callback: e => { 6339 fetch(`https://api.mapy.cz/v1/elevation?lang=cs&positions=${e.latlng.lng}%2C${e.latlng.lat}&apikey=${giga.mapycom.APIKEY}`) 6340 .then(response => response.json()) 6341 .then(data => { 6342 L.popup() 6343 .setLatLng(e.latlng) 6344 .setContent(`${data.items[0].elevation} m`) 6345 .openOn(giga.mapycom.map); 6346 }) 6347 .catch(error => console.error('Problem! ', error)); 6348 } 6349 }, 6350 { 6351 text: 'Pokaż profil widzialnoÅci', 6352 callback: e => { 6353 fetch(`https://api.mapy.cz/v1/elevation?lang=cs&positions=${e.latlng.lng}%2C${e.latlng.lat}&apikey=${giga.mapycom.APIKEY}`) 6354 .then(response => response.json()) 6355 .then(data => { 6356 const point = {lat: e.latlng.lat, lon: e.latlng.lng, elevation: Math.round(data.items[0].elevation), title: 'Punkt na mapie' }; 6357 giga.lineOfSight.checkFromCurrent(point); 6358 }) 6359 .catch(error => console.error('Problem! ', error)); 6360 } 6361 }, 6362 { 6363 text: 'Szukaj obiektów w pobliżu (lokalne)', 6364 mouseover: e => { 6365 showMouseOverRectangle(e); 6366 }, 6367 mouseout: e => { 6368 console.log('mouseout' ,e); 6369 hideMouseOverRectangle(e); 6370 }, 6371 callback: async (e) => { 6372 await gatherObjects(e); 6373 } 6374 }, 6375 { 6376 text: 'Szukaj obiektów w pobliżu (Overpass)', 6377 mouseover: e => { 6378 showMouseOverRectangle(e); 6379 }, 6380 mouseout: e => { 6381 hideMouseOverRectangle(e); 6382 }, 6383 callback: async (e) => { 6384 await gatherObjects(e, true); 6385 } 6386 }, 6387 '-', 6388 { 6389 text: 'WyÅrodkuj tu mapÄ', 6390 callback: e => { 6391 giga.mapycom.map.panTo(e.latlng); 6392 } 6393 }, 6394 { 6395 text: 'Ustaw azymut na ten punkt (Alt-click)', 6396 callback: e => { 6397 const info = k.info; 6398 const azimuth = giga.getBearing(+info.lat, +info.lon, e.latlng.lat, e.latlng.lng); 6399 const hpos = giga.azimuthToHpos(azimuth); 6400 k.call('moveto('+hpos+',get(view.vlookat))'); 6401 } 6402 }, 6403 '-', 6404 { 6405 text: 'Zamknij', 6406 callback: e => { 6407 $(giga.mapycom.mapContainer).hide(); 6408 } 6409 } 6410 ] 6411 }; 6412 6413 giga.mapycom.map = L.map(giga.mapycom.mapContainer.id, menuOptions).setVie
6413w(center, zoom); 6414 6415 const countryBounds = { 6416 'WORLD': [[90,-180],[-90,180]], 6417 'SK': [[47.7584288, 16.8799829], [49.5715740, 22.5581376]], 6418 'CZ': [[48.5553052, 12.2401111], [51.1172677, 18.8531441]], 6419 'CH': [[45.7769477, 6.02260949], [47.8308275, 10.4427014]], // [[45.398181, 5.140242], [48.230651, 11.47757]] 6420 'PL': [[49.0273953, 14.0745211], [54.8515359, 24.0299857]] 6421 } 6422 6423 const tileLayers = { 6424 'mapy.com: podstawa': 6425 L.tileLayer(`https://api.mapy.com/v1/maptiles/basic/256/{z}/{x}/{y}?apikey=${giga.mapycom.APIKEY}`, { 6426 minZoom: 0, 6427 maxZoom: 19, 6428 bounds: countryBounds.WORLD, 6429 name: 'mapy.com', 6430 attribution: 'mapy.com podstawa <a href="https://api.mapy.com/copyright" target="_blank">© Seznam.cz a.s. a dalÅ¡Ã</a>', 6431 getURL: (lat,lon,zoom=16) => `https://mapy.com/en/zakladni?x=${lon}&y=${lat}&z=${zoom}`, 6432 }), 6433 'mapy.com: turystyczna': 6434 L.tileLayer(`https://api.mapy.com/v1/maptiles/outdoor/256/{z}/{x}/{y}?apikey=${giga.mapycom.APIKEY}`, { 6435 minZoom: 0, 6436 maxZoom: 19, 6437 bounds: countryBounds.WORLD, 6438 name: 'mapy.com', 6439 attribution: 'mapy.com turystyczna <a href="https://api.mapy.com/copyright" target="_blank">© Seznam.cz a.s. a dalÅ¡Ã</a>', 6440 getURL: (lat,lon,zoom=16) => `https://mapy.com/en/turisticka?x=${lon}&y=${lat}&z=${zoom}`, 6441 }), 6442 'mapy.com: lotnicza': 6443 L.tileLayer(`https://api.mapy.com/v1/maptiles/aerial/256/{z}/{x}/{y}?apikey=${giga.mapycom.APIKEY}`, { 6444 minZoom: 0, 6445 maxZoom: 19, 6446 bounds: countryBounds.WORLD, 6447 name: 'mapy.com', 6448 attribution: 'mapy.com ortofoto WMTS <a href="https://api.mapy.com/copyright" target="_blank">© Seznam.cz a.s. a dalÅ¡Ã</a>', 6449 getURL: (lat,lon,zoom=16) => `https://mapy.com/en/letecka?x=${lon}&y=${lat}&z=${zoom}`, 6450 }), 6451 6452 'Geoportal: PRNG': 6453 L.tileLayer.wms('https://mapy.geoportal.gov.pl/wss/service/PZGiK/PRNG/WMS/GeographicalNames?', { 6454 layers: 'O1_UrzedoweNazwyObiektowFizjograficznych,O2_ZestandaryzowaneNazwyObiektowFizjograficznych', 6455 minZoom: 5, 6456 maxZoom: 22, 6457 bounds: countryBounds.PL, 6458 format: 'image/png', 6459 transparent: true, 6460 version: '1.3.0', 6461 tiled: true, 6462 crossorigin: true, 6463 attribution: "Geoportal PRNG © GÅówny UrzÄ d Geodezji i Kartografii" 6464 }), 6465 6466 'Geoportal: ortofotomapa HR': 6467 L.tileLayer.wms('https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMS/HighResolution?', { 6468 layers: 'Raster', 6469 minZoom: 5, 6470 maxZoom: 22, 6471 bounds: countryBounds.PL, 6472 format: 'image/png', 6473 transparent: false, 6474 version: '1.1.0', 6475 tiled: true, 6476 crossorigin: true, 6477 attribution: "Geoportal ortofoto HR © GÅówny UrzÄ d Geodezji i Kartografii" 6478 }), 6479 6480 /* 6481 'Geoportal: ortofotomapa SR': 6482 L.tileLayer.wms('https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMS/StandardResolution?', { 6483 layers: 'Raster', 6484 minZoom: 5, 6485 maxZoom: 20, 6486 bounds: countryBounds.PL, 6487 format: 'image/png', 6488 transparent: false, 6489 version: '1.1.0', 6490 tiled: true, 6491 crossorigin: true, 6492 attribution: "© GÅówny UrzÄ d Geodezji i Kartografii" 6493 }), 6494 */ 6495 6496 'Geoportal: ortofotomapa (WMTS)': 6497 L.tileLayer(`https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMTS/StandardResolution?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ORTOFOTOMAPA&STYLE=default&FORMAT=image/jpeg&TILEMATRIXSET=EPSG:3857&TILEMATRIX=EPSG:3857:{z}&TILEROW={y}&TILECOL={x}`, { 6498 minZoom: 0, 6499 maxZoom: 19, 6500 bounds: countryBounds.PL, 6501 attribution: "Geoportal ortofoto WMTS © GÅówny UrzÄ d Geodezji i Kartografii" 6502 }), 6503 6504 'ZBGIS: základna topo 1:10000': 6505 L.tileLayer(`https://zbgis.skgeodesy.sk/zbgis/rest/services/ZMSR_10/MapServer/tile/{z}/{y}/{x}?blankTile=false`, { 6506 minZoom: 8, 6507 maxZoom: 19, 6508 maxNativeZoom: 17, 6509 bounds: countryBounds.SK, 6510 transparent: false, 6511 attribution: 'ZBGIS topo 10k © Geodetický a kartografický ústav Bratislava' 6512 }), 6513 6514 'ZBGIS: mapa topo': 6515 L.tileLayer.wms('https://zbgisws.skgeodesy.sk/retm_wms/service.svc/get', { 6516 layers: '1', 6517 minZoom: 8, 6518 maxZoom: 18, 6519 bounds: countryBounds.SK, 6520 format: 'image/png', 6521 transparent: false, 6522 attribution: 'ZBGIS topo © Geodetický a kartografický ústav Bratislava' 6523 }), 6524 6525 'ZBGIS: ortofotomapa': 6526 L.tileLayer.wms('https://zbgisws.skgeodesy.sk/zbgis_ortofoto_wms/service.svc/get', { 6527 layers: '1', 6528 bounds: countryBounds.SK, 6529 format: 'image/jpeg', 6530 transparent: false, 6531 attribution: 'ZBGIS ortofoto © Geodetický a kartografický ústav Bratislava' 6532 }), 6533 6534 'mapy.dennikn.sk': 6535 L.tileLayer(`https://static.mapy.hiking.sk/topo/{z}/{x}/{y}.png`, { 6536 minZoom: 9, 6537 maxZoom: 18, 6538 maxNativeZoom: 15, 6539 bounds: [countryBounds.CZ, countryBounds.SK], 6540 attribution: "SHOCart, https://hiking.dennikn.sk/", 6541 getURL: (lat,lon,zoom=15) => `https://mapy.dennikn.sk/?y=${lat}&x=${lon}&zoom=${zoom}`, 6542 }), 6543 6544 'ÄÃZK: ortofotomapa': 6545 L.tileLayer.wms('https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer?', { 6546 layers: '0', 6547 bounds: countryBounds.CZ, 6548 format: 'image/png', 6549 transparent: true, 6550 version: '1.1.0', 6551 tiled: true,
6552 attribution: 'ÄÃZK ortofoto © Czech Geoportal (ÄÃZK)' 6553 }), 6554 6555 'ÄÃZK: topo 1:5000': 6556 L.tileLayer.wms('https://ags.cuzk.gov.cz/arcgis1/services/ZTM/ZTM5/MapServer/WMSServer?', { 6557 layers: '0', 6558 bounds: countryBounds.CZ, 6559 format: 'image/png', 6560 transparent: true, 6561 version: '1.1.0', 6562 tiled: true, 6563 attribution: 'ÄÃZK topo © Czech Geoportal (ÄÃZK)' 6564 }), 6565 6566 "Google Streets": 6567 L.tileLayer('https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&hl=pl', { 6568 maxZoom: 20, 6569 bounds: countryBounds.WORLD, 6570 subdomains:['mt0','mt1','mt2','mt3'], 6571 attribution: 'Google Streets © Google', 6572 getURL: (lat,lon,zoom=16) => `https://www.google.com/maps/@${lat},${lon},${zoom}z`, 6573 6574 }), 6575 "Google Hybrid": 6576 L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&hl=pl', { 6577 maxZoom: 20, 6578 bounds: countryBounds.WORLD, 6579 subdomains:['mt0','mt1','mt2','mt3'], 6580 attribution: 'Google Hybrid © Google', 6581 getURL: (lat,lon,zoom=16) => `https://www.google.com/maps/@${lat},${lon},${zoom}z/data=!3m1!1e3!5m1!1e4`, 6582 }), 6583 "Google Satellite": 6584 L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&hl=pl', { 6585 maxZoom: 20, 6586 bounds: countryBounds.WORLD, 6587 subdomains:['mt0','mt1','mt2','mt3'], 6588 attribution: 'Google Sat © Google', 6589 getURL: (lat,lon,zoom=16) => `https://www.google.com/maps/@${lat},${lon},${zoom}z/data=!3m1!1e3!5m1!1e4`, 6590 }), 6591 "Google Terrain": 6592 L.tileLayer('https://{s}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}&hl=pl', { 6593 maxZoom: 20, 6594 bounds: countryBounds.WORLD, 6595 subdomains:['mt0','mt1','mt2','mt3'], 6596 attribution: 'Google Terrain © Google', 6597 getURL: (lat,lon,zoom=16) => `https://www.google.com/maps/@${lat},${lon},${zoom}z/data=!3m1!1e3!5m1!1e4!6m1!1e1`, 6598 }), 6599 6600 /* 6601 'Geoportal BDOT10k': 6602 new L.TileLayer.WMTS('https://mapy.geoportal.gov.pl/wss/service/WMTS/guest/wmts/BDOT10k', { 6603 crs: new L.Proj.CRS( 6604 "EPSG:2180", 6605 "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs", 6606 {} 6607 ), 6608 layer: "", 6609 style: "", 6610 bounds: countryBounds.PL, 6611 tilematrixset: "EPSG:2180", 6612 format: "image/png", 6613 attribution: "© GÅówny UrzÄ d Geodezji i Kartografii", 6614 crossorigin: true 6615 }), 6616 */ 6617 6618 'Open Topo Map': 6619 L.tileLayer(`https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png`, { 6620 minZoom: 0, 6621 maxNativeZoom: 17, 6622 maxZoom: 18, 6623 subdomains:['a','b','c'], 6624 bounds: countryBounds.WORLD, 6625 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', 6626 getURL: (lat,lon,zoom=16) => `https://www.opentopomap.org/?mlat=${lat}&mlon=${lon}&zoom=${zoom}` 6627 }), 6628 6629 'Open Street Map': 6630 L.tileLayer(`https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`, { 6631 minZoom: 0, 6632 maxZoom: 19, 6633 bounds: countryBounds.WORLD, 6634 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', 6635 getURL: (lat,lon,zoom=16) => `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&zoom=${zoom}` 6636 }), 6637 6638 'Swisstopo: mapa topo': 6639 L.tileLayer('https://wmts{s}.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-farbe/default/current/3857/{z}/{x}/{y}.jpeg', { 6640 subdomains: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 6641 minZoom: 2, 6642 maxZoom: 20, 6643 bounds: countryBounds.CH, 6644 attribution: '© <a href="https://www.swisstopo.admin.ch/">swisstopo</a>' 6645 }), 6646 6647 'Swisstopo: ortofotomapa': 6648 L.tileLayer('https://wmts{s}.geo.admin.ch/1.0.0/ch.swisstopo.swissimage/default/current/3857/{z}/{x}/{y}.jpeg', { 6649 subdomains: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 6650 minZoom: 2, 6651 maxZoom: 20, 6652 bounds: countryBounds.CH, 6653 attribution: '© <a href="https://www.swisstopo.admin.ch/">swisstopo</a>' 6654 }) 6655 6656 }; 6657 6658 const LogoControl = L.Control.extend({ 6659 options: { 6660 position: 'bottomleft', 6661 }, 6662 onAdd: function (map) { 6663 const container = L.DomUtil.create('div'); 6664 const link = L.DomUtil.create('a', '', container); 6665 6666 link.setAttribute('href', 'http://mapy.com/'); 6667 link.setAttribute('target', '_blank'); 6668 link.innerHTML = '<img src="https://api.mapy.com/img/api/logo.svg" />'; 6669 L.DomEvent.disableClickPropagation(link); 6670 6671 return container; 6672 }, 6673 }); 6674 const logoControl = new LogoControl(); 6675 6676 giga.mapycom.map.on('layeradd', function (e) { 6677 //console.log('layeradd'); 6678 //console.log(e); 6679 if (e.layer.options.name?.includes('mapy.com')) { 6680 logoControl.addTo(giga.mapycom.map); 6681 } 6682 }); 6683 6684 giga.mapycom.map.on('layerremove', function (e) { 6685 //console.log('layerremove'); 6686 //console.log(e); 6687 if (e.layer.options.name?.includes('mapy.com')) { 6688 logoControl.remove(); 6689 } 6690 }); 6691 6692 L.control.mousePosition({ 6693 latFormatter: n => n.toFixed(5), 6694 lngFormatter: n => n.toFixed(5), 6695 prefixFormatter: latlng => { 6696 const info = k.info; 6697 const {distance, bearing} = giga.getDistanceAndBearing(info.lat,info.lon,latlng.lat,latlng.lng); 6698 let zoomText = `Zoom <span class="zoom-level-info">${giga.mapycom.map.getZoom()}</span>`;
6699 if (typeof giga.mapycom.layerControl.getCurrentLayer().layer.options.getURL == 'function') { 6700 const center = giga.mapycom.map.getCenter(); 6701 zoomText = `<a href="${giga.mapycom.layerControl.getCurrentLayer().layer.options.getURL(center.lat, center.lng, giga.mapycom.map.getZoom())}" target="_blank">${zoomText}</a>`; 6702 } 6703 return `${zoomText} : <b>${distance.toFixed(2)} km</b> : ${bearing.toFixed(2)}° : `; 6704 }, 6705 position: 'bottomleft', 6706 separator: ', ', 6707 emptyString: '---', 6708 }).addTo(giga.mapycom.map); 6709 6710 tileLayers['mapy.com: turystyczna'].addTo(giga.mapycom.map); 6711 giga.mapycom.recentLayers = [tileLayers['mapy.com: turystyczna'], tileLayers['mapy.com: lotnicza']]; 6712 6713 6714 function updateBaseLayerControl(map, tileLayers) { 6715 6716 const mapBounds = map.getBounds(); 6717 const visibleBaseLayers = {}; 6718 6719 //console.log('updating visible base layers list'); 6720 6721 Object.entries(tileLayers).forEach(([name, layer]) => { 6722 //console.log(mapBounds); 6723 //console.log(layer.options.bounds); 6724 if (layer.options.bounds && ( Array.isArray(layer.options.bounds[0][0]) ? layer.options.bounds.some(b => mapBounds.intersects(b)) : mapBounds.intersects(layer.options.bounds)) ) { 6725 //console.log('adding ' + name); 6726 visibleBaseLayers[name] = layer; 6727 } else { 6728 //console.log('removing ' + name); 6729 } 6730 }); 6731 6732 // Remove old control and add new one 6733 if (giga.mapycom.layerControl) { 6734 giga.mapycom.map.removeControl(giga.mapycom.layerControl); 6735 } 6736 6737 giga.mapycom.layerControl = L.control.layers(visibleBaseLayers, null).addTo(map); 6738 } 6739 6740 giga.mapycom.layerControl = L.control.layers(tileLayers); 6741 giga.mapycom.layerControl.addTo(giga.mapycom.map); 6742 updateBaseLayerControl(giga.mapycom.map, tileLayers); 6743 6744 giga.mapycom.objectsLayer = L.featureGroup(); 6745 giga.mapycom.markerLayer = L.featureGroup(); 6746 giga.mapycom.lineLayer = L.featureGroup(); 6747 giga.mapycom.contextMenuMarkerLayer = L.featureGroup(); 6748 6749 giga.mapycom.objectsLayer.addTo(giga.mapycom.map); 6750 giga.mapycom.markerLayer.addTo(giga.mapycom.map); 6751 giga.mapycom.lineLayer.addTo(giga.mapycom.map); 6752 giga.mapycom.contextMenuMarkerLayer.addTo(giga.mapycom.map); 6753 6754 giga.mapycom.loadObjects(); 6755 6756 giga.mapycom.map.on('moveend', () => updateBaseLayerControl(giga.mapycom.map, tileLayers)); 6757 giga.mapycom.map.on('click', e => { 6758 if (e.originalEvent.altKey) { 6759 const info = k.info; 6760 const azimuth = giga.getBearing(+info.lat, +info.lon, e.latlng.lat, e.latlng.lng); 6761 const hpos = giga.azimuthToHpos(azimuth); 6762 k.call('moveto('+hpos+',get(view.vlookat))'); 6763 } 6764 }); 6765 6766 giga.mapycom.map.on('contextmenu.item.mouseover', e => { 6767 //console.log('contextmenu.item.mouseover'); 6768 //console.log(e); 6769 }); 6770 giga.mapycom.map.on('contextmenu.item.mouseout', e => { 6771 //console.log('contextmenu.item.mouseout'); 6772 //console.log(e); 6773 }); 6774 giga.mapycom.map.on('contextmenu.show', e => { 6775 //console.log('contextmenu.show'); 6776 //console.log(e); 6777 L.circleMarker(e.latlng, {radius: 4, color: '#f00', opacity: 0.7}).addTo(giga.mapycom.contextMenuMarkerLayer); 6778 //if (e.relatedTarget) return; 6779 e.contextmenu.updateItem(0, { 6780 text: `<b>${e.latlng.lat.toFixed(6)}, ${e.latlng.lng.toFixed(6)}</b>`, 6781 callback: e => { 6782 giga.clipboard.copy(`lat="${e.latlng.lat.toFixed(5)}" lon="${e.latlng.lng.toFixed(5)}"`); 6783 } 6784 }); 6785 if (typeof giga.mapycom.layerControl.getCurrentLayer().layer.options.getURL == 'function') { 6786 e.contextmenu.showItem(4); 6787 } else { 6788 e.contextmenu.hideItem(4); 6789 } 6790 6791 if (giga.mapycom.objectsLayer.getLayers().length>0) { 6792 e.contextmenu.insertItem({ 6793 text: 'UsuÅ zaÅadowane obiekty', 6794 callback: e => { 6795 showScreenMessage(`Obiekty usuniÄte`, {category:'map-show-objects'}); 6796 giga.mapycom.objectsLayer?.clearLayers(); 6797 } 6798 }, 7); 6799 } else { 6800 e.contextmenu.insertItem({ 6801 text: 'ZaÅaduj obiekty z bazy', 6802 callback: e => { 6803 giga.mapycom.loadObjects(); 6804 } 6805 }, 7); 6806 } 6807 6808 if (giga.mapycom.starLayer) { 6809 e.contextmenu.insertItem({ 6810 text: 'Schowaj, co widaÄ', 6811 callback: e => { 6812 giga.mapycom.removeStarLayer(); 6813 } 6814 }, 8); 6815 } else { 6816 e.contextmenu.insertItem({ 6817 text: 'Pokaż, co widaÄ', 6818 callback: e => { 6819 giga.mapycom.addStarLayer(); 6820 } 6821 }, 8); 6822 } 6823 6824 }); 6825 giga.mapycom.map.on('contextmenu.hide', e => { 6826 e.contextmenu.removeItem(8); 6827 e.contextmenu.removeItem(7); 6828 //e.contextmenu.removeItem(0); 6829 giga.mapycom.contextMenuMarkerLayer.clearLayers(); 6830 }); 6831 6832 giga.mapycom.map.on('baselayerchange', e => { 6833 const current = e.layer; 6834 if (giga.mapycom.recentLayers[0] !== current) { 6835 giga.mapycom.recentLayers.unshift(current); 6836 if (giga.mapycom.recentLayers.length > 2) giga.mapycom.recentLayers.pop(); 6837 } 6838 giga.mapycom.map.contextmenu.toggleItem(4, typeof giga.mapycom.layerControl.getCurrentLayer().layer.options.getURL == 'function'); 6839 6840 }); 6841 6842 giga.mapycom.map.on('keydown', function(e) { 6843 if (e.originalEvent.key.toLowerCase() === 'z' && giga.mapycom.recentLayers.length === 2) { 6844 giga.mapycom.map.eachLayer(layer => { 6845 if (Object.values(tileLayers).includes(layer)) { 6846 giga.mapycom.map.removeLayer(layer); 6847 } 6848 }); 6849 giga.mapycom.map.addLayer(giga.mapycom.recentLayers[1]); 6850 // Swap the layers 6851 recentLayers = [giga.mapycom.recentLayers[1], giga.mapycom.recentLayers[0]]; 6852 } 6853 }); 6854 6855 giga.mapycom.map.on('zoomend', e => { 6856 const zoomInfo = giga.mapycom.mapContainer.querySelector('.zoom-level-info'); 6857 const zoom = giga.mapycom.map.getZoom(); 6858 if (zoomInfo) zoomInfo.textContent = zoom; 6859 // hide objects layer when zoomed out 6860 if (zoom <= 8) { 6861 if (giga.mapycom.map.hasLayer(giga.mapycom.objectsLayer)) { 6862 giga.mapycom.map.removeLayer(giga.mapycom.objectsLayer); 6863 } 6864 } else { 6865 if (!giga.mapycom.map.hasLayer(giga.mapycom.objectsLayer)) { 6866 giga.mapycom.map.addLayer(giga.mapycom.objectsLayer); 6867 } 6868 } 6869 }); 6870 6871 } else { 6872 $("#mapycom-map").show(); 6873 } 6874 6875 giga.mapycom.markerLayer?.clearLayers(); 6876 giga.mapycom.lineLayer?.clearLayers(); 6877 6878 // POV marker 6879 const markerTitle = `${name}: ${lat}, ${lon}`; 6880 const marker = L.marker(center, { 6881 opacity: 0.9, 6882 title: markerTitle, 6883 icon: new L.Icon.Default({ 6884 imagePath: '/giga/lib/leaflet-1.9.4/images/', 6885 iconUrl: 'marker-icon-red.png', 6886 iconRetinaUrl: 'marker-icon-red-2x.png' 6887 }), 6888 }); 6889 marker.bindPopup(markerTitle); 6890 marker.addTo(giga.mapycom.markerLayer); 6891 6892 if (Array.isArray(opts?.lines)) {
6893 opts.lines.forEach( l => giga.mapycom.addLine(l[0].lat,l[0].lon, l[1].lat,l[1].lon) ); 6894 } 6895 return giga.mapycom; 6896 6897 }, 6898 6899 removeStarLayer: () => { 6900 if (!giga.mapycom.starLayer) return; 6901 giga.mapycom.starLayer.clearLayers(); 6902 giga.mapycom.starLayer = null; 6903 }, 6904 6905 addStarLayer: () => { 6906 if (giga.mapycom.starLayer) return; 6907 const info = k.info; 6908 const latlng = [+info.lat, +info.lon]; 6909 const hsList = giga.currentPanorama.getHotspotsList().filter( (h) => { 6910 const p = hotspots.points.get(h.id); 6911 return hasValue(p?.lat, p?.lon); 6912 }).sort( (a, b) => +a.ath >= +b.ath ); 6913 6914 const aStart = +info.azimuthleft; 6915 const aStop = +info.azimuthright; 6916 var range, direction; 6917 if (aStop>=aStart) { 6918 range = aStop-aStart; 6919 direction = aStart + range/2; 6920 } else { 6921 range = aStop+360-aStart; 6922 direction = aStart + range/2; 6923 if (direction>=360) direction -= 360; 6924 } 6925 6926 giga.mapycom.starLayer = L.featureGroup(); 6927 6928 const wholeView = L.semiCircle(latlng, { 6929 interactive: false, 6930 fill: true, 6931 fillColor:'#444444', 6932 fillOpacity: 0.11, 6933 color: '#444444', 6934 opacity: 0.0, 6935 sticky: true, 6936 radius: info.range ? +info.range*1000 : 100000 6937 }).setDirection(+direction,+range).addTo(giga.mapycom.starLayer); 6938 6939 const lines = L.featureGroup(); 6940 hsList.forEach( (h) => { 6941 let p = hotspots.points.get(h.id); 6942 L.polyline([latlng, [+p.lat,+p.lon]], { 6943 keyboard: false, 6944 title: p.name, 6945 color: '#ff0000', 6946 opacity: 0.66, 6947 weight: 2 6948 }).on('click', function() { 6949 giga.moveToHotspot(h.name); 6950 }).bindTooltip(h.title, { 6951 keyboard: false, 6952 interactive: false, 6953 sticky: true 6954 }).addTo(lines); 6955 }); 6956 lines.addTo(giga.mapycom.starLayer); 6957 6958 L.circleMarker(latlng, { 6959 radius: 3, 6960 fill: '#00aa00', 6961 color: '#00aa00' 6962 }).addTo(giga.mapycom.starLayer); 6963 6964 giga.mapycom.starLayer.addTo(giga.mapycom.map); 6965 giga.mapycom.map.fitBounds(lines.getBounds()); 6966 6967 }, 6968 6969 addLine: function(lat1,lon1,lat2,lon2, temporary=false) { 6970 6971 const points = [ 6972 [lat1, lon1], 6973 [lat2, lon2] 6974 ]; 6975 const options = { 6976 color: temporary ? "#01e" : "#f00", 6977 weight: 2, 6978 opacity: 0.9 6979 }; 6980 const polyline = L.polyline(points, options); 6981 polyline.addTo(giga.mapycom.lineLayer); 6982 6983 const greatCircleOptions = { 6984 color: temporary ? "#09e" : "#f80", 6985 weight: 2, 6986 opacity: 0.7, 6987 contextmenu: true, 6988 contextmenuInheritItems: false, 6989 contextmenuItems: [{ 6990 text: 'KoÅo wielkie', 6991 callback: () => {} 6992 }] 6993 }; 6994 const greatCircleLine = L.geodesic(points, greatCircleOptions); 6995 greatCircleLine.addTo(giga.mapycom.lineLayer); 6996 6997 }, 6998 6999 addPoint: function(lat,lon, title='') { 7000 if (giga.mapycom.map) { 7001 const marker = L.marker([lat, lon], { 7002 opacity: 0.9, 7003 title: title, 7004 icon: new L.Icon.Default({ 7005 imagePath: '/giga/lib/leaflet-1.9.4/images/', 7006 iconUrl: 'marker-icon-yellow.png', 7007 iconRetinaUrl: 'marker-icon-yellow-2x.png' 7008 }), 7009 }); 7010 marker.addTo(giga.mapycom.markerLayer); 7011 } 7012 }, 7013 7014 removeLines: function() { 7015 if (giga.mapycom.lineLayer) { 7016 giga.mapycom.lineLayer.clearLayers(); 7017 } 7018 }, 7019 7020 flyTo: function(lat, lon) { 7021 if (giga.mapycom.map) { 7022 giga.mapycom.map.panTo([lat,lon]); 7023 } 7024 }, 7025 7026 flyToWithLine: function(lat, lon, startLat, startLon) { 7027 console.log(lat, lon, startLat, startLon); 7028 if (giga.mapycom.map) { 7029 giga.mapycom.removeLines(); 7030 giga.mapycom.addLine(startLat, startLon, lat, lon, true); 7031 giga.mapycom.map.panTo([lat,lon]); 7032 } 7033 }, 7034 7035 search: () => { 7036 alert('search not implemented!'); 7037 }, 7038 7039 reverseGeocode: async (lat, lon) => { 7040 const response = await fetch(`https://api.mapy.com/v1/rgeocode?lon=${lon}&lat=${lat}&lang=pl&apiKey=${giga.mapycom.APIKEY}&type=poi`); 7041 return await response.json(); 7042 }, 7043 7044 loadObjects: () => { 7045 const objects = giga.getObjectsFromBounds(); 7046 giga.mapycom.objectsLayer?.clearLayers(); 7047 const markerCluster = L.markerClusterGroup({ 7048 maxClusterRadius: 45 7049 }); 7050 showScreenMessage(`Znaleziono ${objects.length} ${odmien(objects.length,'obiekt','obiekty','obiektów')}`, {category:'map-show-objects'}); 7051 const hotspotIDs = new Set(giga.currentPanorama.getHotspotsList().map(h => h.id)); 7052 const redDotIcon = new L.Icon.Default({ 7053 iconUrl: 'marker-icon-red-fill.png', 7054 iconRetinaUrl: 'marker-icon-red-fill-2x.png' 7055 });
7056 objects.forEach(p => { 7057 let markerOptions = { 7058 title: p.names[0] 7059 }; 7060 const isOnPano = hotspotIDs.has(p.id); 7061 if (isOnPano) markerOptions.icon = redDotIcon; 7062 const marker = L.marker([p.lat, p.lon], markerOptions).on('click', e => { 7063 if (isOnPano) { 7064 const hs = giga.currentPanorama.getHotspotsList().find(h => h.id==p.id); 7065 if (hs?.name) giga.moveToHotspotAndClick(hs.name); 7066 } else { 7067 giga.showPointInfo(p.id); 7068 const bearing = giga.getBearing(+k.info.lat,+k.info.lon,p.lat,p.lon); 7069 const corrAz = (360 + bearing - giga.azimuthCorrection) % 360; 7070 const corrHpos = giga.azimuthToHpos(corrAz); 7071 k.call(`moveto(${corrHpos},get(view.vlookat))`); 7072 } 7073 }); 7074 markerCluster.addLayer(marker); 7075 }); 7076 giga.mapycom.objectsLayer.addLayer(markerCluster); 7077 } 7078 7079 7080 }, 7081 7082 lineOfSight: { 7083 infowindow: null, 7084 /** 7085 * Check if point B is visible from point A considering Earth's curvature, 7086 * atmospheric refraction, and terrain elevation. 7087 * 7088 * @param {Object} pointA - {lat: number, lon: number, elevation: number} 7089 * @param {Object} pointB - {lat: number, lon: number, elevation: number, height: number} 7090 * @param {number} samples - Number of elevation samples between points (default: 50) 7091 7092 * @returns {Promise<Object>} - {visible: boolean, reason: string, blocker: Object|null} 7093 */ 7094 clear: () => { 7095 if (giga.lineOfSight.infowindow) { 7096 giga.lineOfSight.infowindow.remove(); 7097 giga.lineOfSight.infowindow = null; 7098 } 7099 }, 7100 7101 checkFromCurrent: async function(point, options = {} ) { 7102 showScreenMessage('profil widzialnoÅci'); 7103 options.samples ??= 800; 7104 if (k.info.refraction) options.refraction ??= +k.info.refraction; 7105 7106 const startLocation = hotspots.points.get(k.info.location); 7107 if (!options.fetchMethod && startLocation?.country?.includes('PL') && point.country?.includes('PL')) { 7108 options.fetchMethod = giga.lineOfSight.fetchMethods.GUGIK; 7109 } 7110 7111 if (point.title) options.caption = point.title; 7112 let objectHeight = 0; 7113 if (!!point.height) objectHeight = Number(point.height); 7114 7115 giga.lineOfSight.check({ 7116 lat: +k.info.lat, lon: +k.info.lon, elevation: +k.info.elevation 7117 }, { 7118 lat: point.lat, lon: point.lon, elevation: point.elevation, height: objectHeight 7119 }, options).then( res => console.log(res) ); 7120 }, 7121 7122 check: async function (pointA, pointB, options = {}) { 7123 options.caption ??= ''; 7124 options.samples ??= 100; 7125 options.refraction ??= 0.13; 7126 options.fetchMethod ??= giga.lineOfSight.fetchMethods.OPENELEVATION; // HEIGIT 7127 7128 const EARTH_RADIUS = 6378137.0; // 6371000; // meters 7129 const REFRACTION_COEFFICIENT = options.refraction; 7130 const MAX_SAMPLES = 5000; 7131 7132 let ret; 7133 7134 if (!giga.lineOfSight.infowindow) { 7135 const el = document.createElement("div"); 7136 el.classList.add("popup-info-window","lineofsight-infowindow"); 7137 giga.lineOfSight.infowindow = el; 7138 document.getElementById('content').append(el); 7139 } else { 7140 giga.lineOfSight.infowindow.classList.remove('blocked','visible'); 7141 giga.lineOfSight.infowindow.replaceChildren(); 7142 } 7143 const close = document.createElement("div"); 7144 close.textContent = 'X'; 7145 close.classList = 'close'; 7146 close.setAttribute('title', 'Zamknij'); 7147 close.onclick = giga.lineOfSight.clear; 7148 giga.lineOfSight.infowindow.append(close); 7149 7150 const infoText = document.createElement("div"); 7151 giga.lineOfSight.infowindow.append(infoText); 7152 7153 // Calculate distance between points 7154 const distance = giga.lineOfSight.vincentyDistance(pointA, pointB); 7155 if (typeof options.sampleInterval==='number' && options.sampleInterval>=1) { 7156 console.log('adjusting samples according to sampleInterval = ' + options.sampleInterval); 7157 options.samples = Math.max(10, Math.min(Math.round(distance / options.sampleInterval), MAX_SAMPLES)); 7158 } 7159 7160 let elevationText = ''; 7161 if (pointB.elevation) { 7162 elevationText = `${pointB.elevation}`; 7163 if (pointB.height>0) elevationText += `+${pointB.height}`; 7164 elevationText = ` (${elevationText} m)`; 7165 } 7166 7167 if (options.caption) { 7168 const div = document.createElement('div'); 7169 div.innerHTML = `<div class="lineofsight-info"><b>${options.caption}</b><br> 7170 <span class="open-mapy-cz-line" data-lat="${pointB.lat.toFixed(5)}" data-lon="${pointB.lon.toFixed(5)}"> 7171 ${pointB.lat.toFixed(5)}, ${pointB.lon.toFixed(5)}</span><span class="elevation-text">${elevationText}</span>. 7172 OdlegÅoÅÄ: ${(distance/1000).toFixed(3)} km.</div>`; 7173 infoText.append(div); 7174 } 7175 7176 infoText.append(makeElement('div',`ŹródÅo danych wysokoÅciowych: <span id="fetch-method" class="hoverable">${options.fetchMethod.short}</span>.`)); 7177 7178 infoText.append(makeElement('div', 7179 `WspóÅczynnik refrakcji: <span id="refraction-coef" class="hoverable">${REFRACTION_COEFFICIENT}</span>. 7180 Liczba próbek: <span id="sample-count" class="hoverable">${options.samples}</span> (co <span id="sample-interval" class="hoverable">${Math.round(distance/(options.samples+1))}</span> m). 7181 `)); 7182 7183 new Dropdown( 7184 document.getElementById("fetch-method"), 7185 Object.entries(giga.lineOfSight.fetchMethods).map(([id, m]) => { 7186 return {label: m.description, value: m}; 7187 }), 7188 (val) => { 7189 let pOptions = { ...options }; 7190 pOptions.fetchMethod = val; 7191 giga.lineOfSight.check(pointA, pointB, pOptions); 7192 }, 7193 { mode: 'hover' } 7194 ); 7195 new Dropdown( 7196 document.getElementById("sample-count"), 7197 [ 7198 {label:"100", value:100}, 7199 {label:"200", value:200}, 7200 {label:"500", value:500}, 7201 {label:"800", value:800}, 7202 {label:"1000", value:1000}, 7203 {label:"2000", value:2000}, 7204 {label:"3000", value:3000}, 7205 {label:"5000", value:5000} 7206 ], 7207 (val) => { 7208 let pOptions = { ...options }; 7209 delete pOptions.sampleInterval; 7210 pOptions.samples = val; 7211 giga.lineOfSight.check(pointA, pointB, pOptions); 7212 }, 7213 { mode: 'hover' } 7214 ); 7215 new Dropdown( 7216 document.getElementById("sample-interval"), 7217 [ 7218 {label:"10 m", value:10}, 7219 {label:"20 m", value:20}, 7220 {label:"50 m", value:50}, 7221 {label:"100 m", value:100}, 7222 {label:"200 m", value:200} 7223 ], 7224 (val) => { 7225 let pOptions = { ...options }; 7226 delete pOptions.samples; 7227 pOptions.sampleInterval = val; 7228 giga.lineOfSight.check(pointA, pointB, pOptions); 7229 }, 7230 { mode: 'hover' } 7231 ); 7232 new Dropdown( 7233 document.getElementById("refraction-coef"), 7234 [ 7235 {label:"0.13", value:0.13}, 7236 {label:"0.14", value:0.14}, 7237 {label:"0.15", value:0.15}, 7238 {label:"0.16", value:0.16}, 7239 {label:"0.17", value:0.17}, 7240 {label:"0.18", value:0.18}, 7241 {label:"0.19", value:0.19}, 7242 {label:"0.20", value:0.20}, 7243 {label:"0.22", value:0.22}, 7244 {label:"0.25", value:0.25} 7245 ], 7246 (val) => { 7247 let pOptions = { ...options }; 7248 pOptions.refraction = val; 7249 giga.lineOfSight.check(pointA, pointB, pOptions); 7250 }, 7251 { mode: 'hover' } 7252 ); 7253 7254 if (distance === null) { 7255 throw new Error('Distance calculation failed to converge'); 7256 } 7257 7258 // Generate intermediate points 7259 const intermediatePoints = giga.lineOfSight.generateIntermediatePoints(pointA, pointB, options.samples); 7260 //console.log(intermediatePoints); 7261 7262 // Fetch elevations for all intermediate points 7263 const elevations = await giga.lineOfSight.fetchElevationsWithBatching(intermediatePoints, {fetchMethod: options.fetchMethod}); 7264 7265 if (!elevationText) { 7266 // jesli nie ma podanej wysokosci, daj tam sciagnieta wysokosc z NMT 7267 pointB.elevation = elevations.at(-1); 7268 elevationText = `${pointB.elevation}`; 7269 if (pointB.height>
72690) elevationText += `+${pointB.height}`; 7270 elevationText = ` (${elevationText} m NMT)`; 7271 document.querySelector('.lineofsight-info .elevation-text').innerHTML = elevationText; 7272 } 7273 7274 if (pointB.height>0) pointB.elevation += pointB.height; 7275 intermediatePoints[0] = { 7276 ...intermediatePoints[0], 7277 index: 0, 7278 elevation: pointA.elevation, 7279 requiredHeight: 0, 7280 obstructionHeight: 0, 7281 obstruction: false, 7282 distanceFromStart: 0, 7283 distanceFromEnd: distance 7284 }; 7285 7286 const lastIndex = intermediatePoints.length - 1; 7287 intermediatePoints[lastIndex] = { 7288 ...intermediatePoints[lastIndex], 7289 index: lastIndex, 7290 elevation: pointB.elevation, 7291 requiredHeight: pointB.elevation, 7292 obstructionHeight: 0, 7293 obstruction: false, 7294 distanceFromStart: distance, 7295 distanceFromEnd: 0 7296 }; 7297 7298 // Check line of sight 7299 for (let i = 1; i < intermediatePoints.length - 1; i++) { 7300 const point = intermediatePoints[i]; 7301 const distFromA = giga.lineOfSight.vincentyDistance(pointA, point); 7302 const distFromB = giga.lineOfSight.vincentyDistance(pointB, point); 7303 7304 // Calculate required height at this point for line of sight 7305 const requiredHeight = giga.lineOfSight.calculateRequiredHeight( 7306 pointA.elevation, 7307 pointB.elevation, 7308 distFromA, 7309 distance, 7310 EARTH_RADIUS, 7311 REFRACTION_COEFFICIENT 7312 ); 7313 7314 intermediatePoints[i] = { 7315 ...intermediatePoints[i], 7316 index: i, 7317 elevation: elevations[i], 7318 requiredHeight: requiredHeight, 7319 obstructionHeight: elevations[i] - requiredHeight, 7320 obstruction: elevations[i] > requiredHeight, 7321 distanceFromStart: distFromA, 7322 distanceFromEnd: distFromB 7323 }; 7324 7325 // Check if terrain blocks the view 7326 if (elevations[i] > requiredHeight) { 7327 7328 if (!ret) { 7329 ret = { 7330 visible: false, 7331 reason: 'Terrain obstruction', 7332 blocker: { 7333 index: i, 7334 lat: point.lat, 7335 lon: point.lon, 7336 elevation: elevations[i], 7337 requiredHeight: requiredHeight, 7338 obstruction: elevations[i] - requiredHeight 7339 }, 7340 maxBlocker: { 7341 index: i, 7342 lat: point.lat, 7343 lon: point.lon, 7344 elevation: elevations[i], 7345 requiredHeight: requiredHeight, 7346 obstruction: elevations[i] - requiredHeight 7347 } 7348 } 7349 } else { 7350 if (elevations[i] - requiredHeight > ret.maxBlocker.obstruction) { 7351 ret.maxBlocker = { 7352 index: i, 7353 lat: point.lat, 7354 lon: point.lon, 7355 elevation: elevations[i], 7356 requiredHeight: requiredHeight, 7357 obstruction: elevations[i] - requiredHeight 7358 } 7359 } 7360 } 7361 7362 } 7363 } 7364 7365 const bars = document.createElement("div"); 7366 bars.classList.add('elevation-bars'); 7367 const barsImage = giga.drawBars(intermediatePoints, {drawCurveLine: true, barWidth: 2, spacing: 0 }); 7368 7369 giga.lineOfSight.infowindow.appendChild(bars); 7370 const display = document.createElement("div"); 7371 giga.lineOfSight.infowindow.appendChild(display); 7372 7373 const zoomable = new ZoomableImage({ 7374 imgEl: barsImage, 7375 containerEl: bars, 7376 width: 410, 7377 height: 160, 7378 statusOverlay: true, 7379 onMouseOver: (args) => { 7380 const sampleIndex = Math.floor(args.unscaledX / 2), thisPoint = intermediatePoints[sampleIndex]; 7381 if (thisPoint) { 7382 const obstructionText = thisPoint.obstruction ? `(przeszkoda: ${Math.round(Math.abs(thisPoint.obstructionHeight))} m)` : `(rezerwa: ${Math.round(Math.abs(thisPoint.obstructionHeight))} m)`; 7383 display.innerHTML = `<span style="color:${thisPoint.obstruction ? 'red' : 'green'}">próbka ${sampleIndex}</span>: 7384 ${(thisPoint.distanceFromStart/1000).toFixed(2)} km, wysokoÅÄ ${thisPoint.elevation} m ${obstructionText}`; 7385 } else { 7386 display.textContent = ''; 7387 } 7388 }, 7389 onClick: (args) => { 7390 const sampleIndex = Math.floor(args.unscaledX / 2), thisPoint = intermediatePoints[sampleIndex]; 7391 const info = k.info; 7392 giga.mapycom.showMap(+thisPoint.lat, +thisPoint.lon, '', { 7393 zoom: 15, 7394 /* 7395 lines: [ 7396 [ {lat: +info.lat, lon: +info.lon}, {lat: +thisPoint.lat, lon: +thisPoint.lon} ] 7397 ] 7398 */ 7399 }).flyToWithLine(+thisPoint.lat, +thisPoint.lon, +info.lat, +info.lon); 7400 } 7401 }); 7402 7403 /* 7404 barsImage.addEventListener('mousemove', (e) => { 7405 const rect = barsImage.getBoundingClientRect(); 7406 const x = e.clientX - rect.left; 7407 const percent = Math.min(100, Math.max(0, (x / rect.width) * 100)).toFixed(1); 7408 const sampleIndex = Math.round(percent*intermediatePoints.length/100), thisPoint = intermediatePoints[sampleIndex]; 7409 const obstructionText = thisPoint.obstruction ? `(przeszkoda: ${Math.round(Math.abs(thisPoint.obstructionHeight))} m)` : `(rezerwa: ${Math.round(Math.abs(thisPoint.obstructionHeight))} m)`; 7410 display.innerHTML = `<span style="color:${thisPoint.obstruction ? 'red' : 'green'}">próbka ${sampleIndex}</span>: 7411 ${(thisPoint.distanceFromStart/1000).toFixed(2)} km, wysokoÅÄ ${thisPoint.elevation} m ${obstructionText}`; 7412 7413 }); 7414 7415 barsImage.addEventListener('click', (e) => { 7416 const rect = barsImage.getBoundingClientRect(); 7417 const x = e.clientX - rect.left; 7418 const percent = Math.min(100, Math.max(0, (x / rect.width) * 100)).toFixed(1); 7419 const sampleIndex = Math.round(percent*intermediatePoints.length/100), thisPoint = intermediatePoints[sampleIndex]; 7420 7421 const info = k.info; 7422 giga.mapycom.showMap(+thisPoint.lat,+thisPoint.lon,'', { 7423 zoom: 15, 7424 }).flyToWithLine(+thisPoint.lat, +thisPoint.lon, +info.lat, +info.lon); 7425 }); 7426 */ 7427 7428 barsImage.addEventListener('mouseleave', () => { 7429 display.textContent = ''; 7430 }); 7431 7432 if (ret) { 7433 giga.lineOfSight.infowindow.classList.remove('visible'); 7434 giga.lineOfSight.infowindow.classList.add('blocked'); 7435 7436 function formatBlockerData(blocker) { 7437 return ` 7438 <td title="do widzialnoÅci brakÅo ${blocker.obstruction.toFixed(1)} m"><b>${Math.round(blocker.obstruction)} m</b></td> 7439 <td title="wysokoÅÄ npm. przeszkody">(${Math.round(blocker.elevation)} m)</td> 7440 <td title="odlegÅoÅÄ do przeszkody">⤝ ${giga.getDistanceVincenty(pointA.lat,pointA.lon,blocker.lat,blocker.lon).toFixed(2)} km</td> 7441 <td title="odlegÅoÅÄ od przeszkody do celu">${giga.getDistanceVincenty(blocker.lat,blocker.lon,pointB.lat,pointB.lon).toFixed(2)} km ⤝</td> 7442 <td title="wspóÅrzÄdne przeszkody - kliknij, żeby pokazaÄ punkt na mapie"><span class="open-mapy-cz-line" data-lat="${blocker.lat.toFixed(5)}" data-lon="${blocker.lon.toFixed(5)}">${blocker.lat.toFixed(3)}, ${blocker.lon.toFixed(3)}</span></td> 7443 `; 7444 } 7445 7446 let blockerText = ''; 7447 blockerText += '<table class="blocker-data">'; 7448 blockerText += `<tr><td>#1</td> ${formatBlockerData(ret.blocker)}</tr>`; 7449 if (ret.maxBlocker.index != ret.blocker.index) blockerText += `<tr><td>Max</td> ${formatBlockerData(ret.maxBlocker)}</tr>`; 7450 blockerText += '</table>'; 7451 7452 infoText.append(makeElement('div',blockerText)); 7453 return ret; 7454 } 7455 // const minReserve = Math.min(...intermediatePoints.map( p => -p.obstructionHeight || 10000 )); 7456 infoText.append(makeElement('div', 'Widoczny!')); 7457 giga.lineOfSight.infowindow.classList.remove('blocked'); 7458 giga.lineOfSight.infowindow.classList.add('visible'); 7459 return { 7460 visible: true, 7461 reason: 'Clear line of sight', 7462 blocker: null 7463 }; 7464 }, 7465 7466 /** 7467 * Calculate the required height at a point for line of sight 7468 */ 7469 calculateRequiredHeight: function (elevA, elevB, distFromA, totalDist, earthRadius, refractionCoef) { 7470 // Linear interpolation of height 7471 const linearHeight = elevA + (elevB - elevA) * (distFromA / totalDist); 7472 7473 // Earth curvature drop (adjusted for refraction) 7474 // Refraction effectively increases Earth's radius 7475 const effectiveRadius = earthRadius / (1 - refractionCoef); 7476 const curvatureDrop = (distFromA * (totalDist - distFromA)) / (2 * effectiveRadius); 7477 7478 return linearHeight - curvatureDrop; 7479 }, 7480 7481 /** 7482 * Calculate distance between two points using Vincenty's formula (inverse problem) 7483 * More accurate than Haversine, accounts for Earth's ellipsoidal shape 7484 */ 7485 vincentyDistance: function (point1, point2) { 7486 const a = 6378137.0; // WGS-84 semi-major axis (meters) 7487 const b = 6356752.314245; // WGS-84 semi-minor axis (meters) 7488 const f = 1 / 298.257223563; // WGS-84 flattening 7489 const Pi180 = Math.PI / 180; 7490 7491 const lat1 = point1.lat * Pi180; 7492 const lat2 = point2.lat * Pi180; 7493 const lon1 = point1.lon * Pi180; 7494 const lon2 = point2.lon * Pi180; 7495 7496 const L = lon2 - lon1; 7497 const U1 = Math.atan((1 - f) * Math.tan(lat1)); 7498 const U2 = Math.atan((1 - f) * Math.tan(lat2)); 7499 const sinU1 = Math.sin(U1); 7500 const cosU1 = Math.cos(U1); 7501 const sinU2 = Math.sin(U2);
7502 const cosU2 = Math.cos(U2); 7503 7504 let lambda = L; 7505 let lambdaP; 7506 let iterLimit = 100; 7507 let cosSqAlpha, sinSigma, cos2SigmaM, cosSigma, sigma; 7508 7509 do { 7510 const sinLambda = Math.sin(lambda); 7511 const cosLambda = Math.cos(lambda); 7512 sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) + 7513 (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * 7514 (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)); 7515 7516 if (sinSigma === 0) return 0; // Co-incident points 7517 7518 cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; 7519 sigma = Math.atan2(sinSigma, cosSigma); 7520 const sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; 7521 cosSqAlpha = 1 - sinAlpha * sinAlpha; 7522 cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha; 7523 7524 if (isNaN(cos2SigmaM)) cos2SigmaM = 0; // Equatorial line 7525 7526 const C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 7527 lambdaP = lambda; 7528 lambda = L + (1 - C) * f * sinAlpha * 7529 (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * 7530 (-1 + 2 * cos2SigmaM * cos2SigmaM))); 7531 } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0); 7532 7533 if (iterLimit === 0) { 7534 // Formula failed to converge, fall back to approximate distance 7535 return null; 7536 } 7537 7538 const uSq = cosSqAlpha * (a * a - b * b) / (b * b); 7539 const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 7540 const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 7541 const deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * 7542 (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * 7543 (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 7544 7545 const distance = b * A * (sigma - deltaSigma); 7546 7547 return distance; 7548 }, 7549 7550 /** 7551 * Generate intermediate points between A and B along the geodesic 7552 * Uses Vincenty's direct formula for accurate ellipsoidal calculations 7553 */ 7554 generateIntermediatePoints: function (pointA, pointB, samples) { 7555 const a = 6378137.0; // WGS-84 semi-major axis 7556 const b = 6356752.314245; // WGS-84 semi-minor axis 7557 const f = 1 / 298.257223563; // WGS-84 flattening 7558 const toRad = Math.PI / 180; 7559 7560 const lat1 = pointA.lat * toRad; 7561 const lon1 = pointA.lon * toRad; 7562 const lat2 = pointB.lat * toRad; 7563 const lon2 = pointB.lon * toRad; 7564 7565 // Calculate total distance and initial/final azimuths using Vincenty inverse 7566 const vincentyInverse = giga.lineOfSight.calculateVincentyInverse(pointA, pointB); 7567 if (!vincentyInverse) { 7568 // Fallback to simple linear interpolation if Vincenty fails 7569 return giga.lineOfSight.generateLinearPoints(pointA, pointB, samples); 7570 } 7571 7572 const totalDistance = vincentyInverse.distance; 7573 const initialAzimuth = vincentyInverse.initialAzimuth; 7574 7575 const points = []; 7576 7577 for (let i = 0; i <= samples; i++) { 7578 const fraction = i / samples; 7579 const distance = totalDistance * fraction; 7580 7581 if (i === 0) { 7582 points.push({ lat: pointA.lat, lon: pointA.lon, elevation: pointA.elevation }); 7583 } else if (i === samples) { 7584 points.push({ lat: pointB.lat, lon: pointB.lon, elevation: pointB.elevation }); 7585 } else { 7586 // Use Vincenty direct formula to find point at distance along geodesic 7587 const point = giga.lineOfSight.vincentyDirect(pointA.lat, pointA.lon, initialAzimuth, distance); 7588 points.push(point); 7589 } 7590 } 7591 7592 return points; 7593 }, 7594 7595 /** 7596 * Calculate Vincenty inverse problem (distance and azimuths between two points) 7597 */ 7598 calculateVincentyInverse: function (point1, point2) { 7599 const a = 6378137.0; 7600 const b = 6356752.314245; 7601 const f = 1 / 298.257223563; 7602 const toRad = Math.PI / 180; 7603 7604 const lat1 = point1.lat * toRad; 7605 const lat2 = point2.lat * toRad; 7606 const lon1 = point1.lon * toRad; 7607 const lon2 = point2.lon * toRad; 7608 7609 const L = lon2 - lon1; 7610 const U1 = Math.atan((1 - f) * Math.tan(lat1)); 7611 const U2 = Math.atan((1 - f) * Math.tan(lat2)); 7612 const sinU1 = Math.sin(U1); 7613 const cosU1 = Math.cos(U1); 7614 const sinU2 = Math.sin(U2); 7615 const cosU2 = Math.cos(U2); 7616 7617 let lambda = L; 7618 let lambdaP; 7619 let iterLimit = 100; 7620 let cosSqAlpha, sinSigma, cos2SigmaM, cosSigma, sigma; 7621 7622 do { 7623 const sinLambda = Math.sin(lambda); 7624 const cosLambda = Math.cos(lambda); 7625 sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) + 7626 (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * 7627 (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)); 7628 7629 if (sinSigma === 0) return { distance: 0, initialAzimuth: 0, finalAzimuth: 0 }; 7630 7631 cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; 7632 sigma = Math.atan2(sinSigma, cosSigma); 7633 const sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; 7634 cosSqAlpha = 1 - sinAlpha * sinAlpha; 7635 cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha; 7636
7637 if (isNaN(cos2SigmaM)) cos2SigmaM = 0; 7638 7639 const C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 7640 lambdaP = lambda; 7641 lambda = L + (1 - C) * f * sinAlpha * 7642 (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * 7643 (-1 + 2 * cos2SigmaM * cos2SigmaM))); 7644 } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0); 7645 7646 if (iterLimit === 0) return null; 7647 7648 const uSq = cosSqAlpha * (a * a - b * b) / (b * b); 7649 const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 7650 const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 7651 const deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * 7652 (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * 7653 (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 7654 7655 const distance = b * A * (sigma - deltaSigma); 7656 7657 const initialAzimuth = Math.atan2(cosU2 * Math.sin(lambda), 7658 cosU1 * sinU2 - sinU1 * cosU2 * Math.cos(lambda)); 7659 7660 return { 7661 distance: distance, 7662 initialAzimuth: initialAzimuth 7663 }; 7664 }, 7665 7666 /** 7667 * Vincenty direct formula: given a point, azimuth, and distance, find destination point 7668 */ 7669 vincentyDirect: function (lat, lon, azimuth, distance) { 7670 const a = 6378137.0; 7671 const b = 6356752.314245; 7672 const f = 1 / 298.257223563; 7673 const toRad = Math.PI / 180; 7674 const toDeg = 180 / Math.PI; 7675 7676 const lat1 = lat * toRad; 7677 const lon1 = lon * toRad; 7678 const alpha1 = azimuth; 7679 const s = distance; 7680 7681 const sinAlpha1 = Math.sin(alpha1); 7682 const cosAlpha1 = Math.cos(alpha1); 7683 7684 const tanU1 = (1 - f) * Math.tan(lat1); 7685 const cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1); 7686 const sinU1 = tanU1 * cosU1; 7687 const sigma1 = Math.atan2(tanU1, cosAlpha1); 7688 const sinAlpha = cosU1 * sinAlpha1; 7689 const cosSqAlpha = 1 - sinAlpha * sinAlpha; 7690 const uSq = cosSqAlpha * (a * a - b * b) / (b * b); 7691 const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 7692 const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 7693 7694 let sigma = s / (b * A); 7695 let sigmaP; 7696 let cos2SigmaM, sinSigma, cosSigma, deltaSigma; 7697 let iterLimit = 100; 7698 7699 do { 7700 cos2SigmaM = Math.cos(2 * sigma1 + sigma); 7701 sinSigma = Math.sin(sigma); 7702 cosSigma = Math.cos(sigma); 7703 deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * 7704 (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * 7705 (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 7706 sigmaP = sigma; 7707 sigma = s / (b * A) + deltaSigma; 7708 } while (Math.abs(sigma - sigmaP) > 1e-12 && --iterLimit > 0); 7709 7710 const tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1; 7711 const lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, 7712 (1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp)); 7713 const lambda = Math.atan2(sinSigma * sinAlpha1, 7714 cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1); 7715 const C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 7716 const L = lambda - (1 - C) * f * sinAlpha * 7717 (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * 7718 (-1 + 2 * cos2SigmaM * cos2SigmaM))); 7719 const lon2 = lon1 + L; 7720 7721 return { 7722 lat: lat2 * toDeg, 7723 lon: lon2 * toDeg 7724 }; 7725 }, 7726 7727 /** 7728 * Fallback: simple linear interpolation 7729 */ 7730 generateLinearPoints: function (pointA, pointB, samples) { 7731 const points = []; 7732 for (let i = 0; i <= samples; i++) { 7733 const fraction = i / samples; 7734 points.push({ 7735 lat: pointA.lat + (pointB.lat - pointA.lat) * fraction, 7736 lon: pointA.lon + (pointB.lon - pointA.lon) * fraction 7737 }); 7738 } 7739 return points; 7740 }, 7741 7742 fetchElevations: async function (points, options={}) { 7743 options.fetchMethod ??= giga.lineOfSight.fetchMethods.OPENELEVATION; 7744 const elevs = await options.fetchMethod.method(points); 7745 return elevs; 7746 }, 7747 7748 fetchElevationsWithBatching: async function (points, options={}) { 7749 options.delay ??= 300; 7750 options.fetchMethod ??= giga.lineOfSight.fetchMethods.OPENELEVATION; 7751 7752 const results = []; 7753 const batches = chunkArray(points, options.fetchMethod.batchSize); 7754 7755 console.log('fetchElevationsWithBatching'); 7756 console.log(options); 7757 console.log(`Processing ${points.length} points in ${batches.length} batches...`); 7758 7759 for (let i = 0; i < batches.length; i++) { 7760 const batch = batches[i]; 7761 7762 try { 7763 //console.log(`Fetching batch ${i + 1}/${batches.length} (${batch.length} points)...`); 7764 showScreenMessage(`ÅciÄ ganie danych z ${options.fetchMethod.description} ${i + 1}/${batches.length}`, { category:'elevation-download', duration:10000 } ); 7765 const elevations = await options.fetchMethod.method(batch); 7766 results.push(...elevations); 7767 7768 // Add delay between batches to avoid overwhelming the server 7769 if (i < batches.length - 1) { 7770 await sleep(options.delay); 7771 } 7772 } catch (error) { 7773 showScreenMessage(`Problem z danymi ${i + 1}/${batches.length}`, { severity: 'error', category:'elevation-download', duration:2000 } ); 7774 console.error(`Error fetching batch ${i + 1}:`, error); 7775 // Add failed batch with null elevations
7776 batch.forEach(coord => { 7777 results.push({ ...coord, elevation: null, error: error.message }); 7778 }); 7779 } 7780 } 7781 showScreenMessage(`Koniec ÅciÄ gania danych`, {category:'elevation-download', duration:1000} ); 7782 7783 return results; 7784 }, 7785 7786 fetchElevationsGugik: async function (points) { 7787 proj4.defs("EPSG:2180","+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); 7788 7789 const locations = points.map(p => { 7790 let pl1992 = proj4("EPSG:2180").forward([+p.lon,+p.lat]); 7791 return { latitude: p.lat, longitude: p.lon, x: Math.round(pl1992[1]), y: Math.round(pl1992[0]) }; 7792 }); 7793 const plLocs = locations.map(p => `${p.x} ${p.y}`).join(','); 7794 //console.log(plLocs); 7795 const url = `https://services.gugik.gov.pl/nmt/?request=GetHByPointList&list=${plLocs}`; //563800 243490,563950 243490,563950 243400 7796 try { 7797 const response = await fetch(url); 7798 if (!response.ok) { 7799 throw new Error(`API error: ${response.status}`); 7800 } 7801 7802 function parseReturn(input) { 7803 const regex = /-?\d+(?:\.\d+)?/g; 7804 const matches = input.match(regex); 7805 7806 if (!matches || matches.length < 3) { 7807 throw new Error("Input must contain at least three numbers separated by spaces."); 7808 return null; 7809 } else { 7810 return matches.map(Number).at(-1); 7811 } 7812 } 7813 7814 const data = await response.text(); 7815 //console.log(data); 7816 return data.split(',').map(str => parseReturn(str)); 7817 } catch (error) { 7818 console.error('Error fetching elevations:', error); 7819 throw error; 7820 return null; 7821 } 7822 }, 7823 7824 fetchElevationsOpenElevation: async function (points, timeoutSeconds = 10) { 7825 const locations = points.map(p => ({ latitude: p.lat, longitude: p.lon })); 7826 7827 const controller = new AbortController(); 7828 const timeoutId = setTimeout(() => controller.abort(), timeoutSeconds * 1000); 7829 7830 try { 7831 const response = await fetch('https://api.open-elevation.com/api/v1/lookup', { 7832 method: 'POST', 7833 headers: { 7834 'Content-Type': 'application/json', 7835 }, 7836 body: JSON.stringify({ locations }), 7837 signal: controller.signal 7838 }); 7839 7840 if (!response.ok) { 7841 showScreenMessage(`BÅÄ d Open Elevation API: ${response.status}`, {severity: 'error'}); 7842 throw new Error(`API error: ${response.status}`); 7843 } 7844 7845 const data = await response.json(); 7846 return data.results.map(r => r.elevation); 7847 } catch (error) { 7848 if (error.name === 'AbortError') { 7849 const msg = `Open Elevation API timeout po ${timeoutSeconds}s`; 7850 showScreenMessage(msg, {severity: 'error'}); 7851 throw new Error(msg); 7852 } 7853 console.error('Error fetching elevations:', error); 7854 throw error; 7855 } finally { 7856 clearTimeout(timeoutId); 7857 } 7858 }, 7859 7860 fetchElevationsHeiGIT: async function (points) { 7861 const ors = new OrsElevation('eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6IjM3ZjI0NzJhY2YzMzQxMDliNjQyMWZhYjM0ODBiMDY4IiwiaCI6Im11cm11cjY0In0='); 7862 const line = await ors.line(points.map( p => [+p.lat.toFixed(6),+p.lon.toFixed(6)] )); 7863 const profile = line.map(p => p.elevation); 7864 return profile; 7865 }, 7866 7867 fetchMethods: { 7868 'OPENELEVATION': { method: args => giga.lineOfSight.fetchElevationsOpenElevation(args), description: 'api.open-elevation.com', short: 'OpenE', batchSize: 310 }, 7869 'GUGIK' : { method: args => giga.lineOfSight.fetchElevationsGugik(args), description: 'GUGiK', short: 'GUGiK', batchSize: 500 }, 7870 'HEIGIT' : { method: args => giga.lineOfSight.fetchElevationsHeiGIT(args), description: 'HeiGIT', short: 'HeiGIT', batchSize: 1000 }, 7871 }, 7872 7873 /** 7874 * Fetch elevations from OpenTopoData API 7875 * Supports multiple datasets: aster30m, etopo1, eudem25m, mapzen, ned10m, nzdem8m, srtm30m, srtm90m 7876 * @param {Array} points - Array of {lat, lon} objects 7877 * @param {string} dataset - Dataset to use (default: 'aster30m' for global 30m resolution) 7878 */ 7879 fetchElevationsOpenTopoData: async function (points, dataset = 'eudem25m') { 7880 // OpenTopoData has a limit of 100 locations per request 7881 const BATCH_SIZE = 100; 7882 const allElevations = []; 7883 7884 for (let i = 0; i < points.length; i += BATCH_SIZE) { 7885 const batch = points.slice(i, i + BATCH_SIZE); 7886 const locations = batch.map(p => `${p.lat},${p.lon}`).join('|'); 7887 7888 try { 7889 const response = await fetch( 7890 `https://api.opentopodata.org/v1/${dataset}?locations=${locations}` 7891 ); 7892 7893 if (!response.ok) { 7894 throw new Error(`API error: ${response.status} ${response.statusText}`); 7895 } 7896 7897 const data = await response.json(); 7898 7899 if (data.status === 'INVALID_REQUEST') { 7900 throw new Error(`Invalid request: ${data.error}`); 7901 } 7902 7903 const elevations = data.results.map(r => { 7904 if (r.elevation === null) { 7905 throw new Error(`No elevation data at ${r.location.lat}, ${r.location.lng}`); 7906 } 7907 return r.elevation; 7908 }); 7909 7910 allElevations.push(...elevations); 7911 7912 // Rate limiting: OpenTopoData requests max 1 call per second for public API 7913 if (i + BATCH_SIZE < points.length) { 7914 await new Promise(resolve => setTimeout(resolve, 1000)); 7915 } 7916 } catch (error) { 7917 console.error('Error fetching elevations:', error); 7918 throw error; 7919 } 7920 } 7921 7922 return allElevations; 7923 } 7924 7925 }, 7926 7927 drawBars: function(values, options = {}) { 7928 const { 7929 barWidth = 2, 7930 spacing = .5, 7931 maxHeight = 150,
7932 color = '#3498db', 7933 color2 = '#dd0000', 7934 drawTopLine = false, 7935 topLineColor = '#44aa44', 7936 drawCurveLine = false, 7937 curveLineColor = '#44aa44' 7938 } = options; 7939 7940 // Create a canvas element 7941 const canvas = document.createElement('canvas'); 7942 const ctx = canvas.getContext('2d'); 7943 7944 // Set canvas dimensions 7945 7946 const canvasWidth = values.length * (barWidth + spacing); 7947 const canvasHeight = maxHeight + 20; 7948 7949 canvas.width = canvasWidth; 7950 canvas.height = canvasHeight; 7951 7952 // Normalize elevation values to fit within maxHeight 7953 const maxElev = Math.max(...values.map(v=>v.elevation || 0)); 7954 const scale = maxHeight / maxElev; 7955 7956 // Draw bars 7957 const barTops = []; 7958 //console.log(values); 7959 values.forEach((v, i) => { 7960 ctx.fillStyle = v.obstruction===true ? color2 : color; 7961 const barHeight = v.elevation * scale; 7962 const x = i * (barWidth + spacing); 7963 const y = canvasHeight - barHeight; 7964 ctx.fillRect(x, y, barWidth, barHeight); 7965 barTops.push({x: x + barWidth/2, y: y }); 7966 }); 7967 //console.log(barTops); 7968 if (drawTopLine) { 7969 console.log('drawing topline'); 7970 ctx.strokeStyle = topLineColor; 7971 ctx.lineWidth = 1; 7972 ctx.beginPath(); 7973 ctx.moveTo(barTops[0].x, barTops[0].y); 7974 ctx.lineTo(barTops[barTops.length - 1].x, barTops[barTops.length - 1].y); 7975 ctx.stroke(); 7976 } 7977 7978 if (drawCurveLine) { 7979 console.log('drawing curve line'); 7980 const tension = 0.5; 7981 const points = values.map((v,i) => { 7982 const yHeight = v.requiredHeight * scale; 7983 const x = i * (barWidth + spacing); 7984 const y = canvasHeight - yHeight; 7985 return {x: x + barWidth/2, y: y }; 7986 }); 7987 7988 ctx.strokeStyle = curveLineColor; 7989 ctx.lineWidth = 1; 7990 ctx.beginPath(); 7991 ctx.moveTo(points[0].x, points[0].y); 7992 7993 for (let i = 0; i < points.length - 1; i++) { 7994 const p0 = points[i - 1] || points[i]; 7995 const p1 = points[i]; 7996 const p2 = points[i + 1]; 7997 const p3 = points[i + 2] || p2; 7998 7999 const cp1x = p1.x + (p2.x - p0.x) / 6 * tension; 8000 const cp1y = p1.y + (p2.y - p0.y) / 6 * tension; 8001 const cp2x = p2.x - (p3.x - p1.x) / 6 * tension; 8002 const cp2y = p2.y - (p3.y - p1.y) / 6 * tension; 8003 8004 ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y); 8005 } 8006 ctx.stroke(); 8007 } 8008 8009 // Return the canvas as an image 8010 const img = new Image(); 8011 img.src = canvas.toDataURL('image/png'); 8012 return img; 8013 }, 8014 8015 /* 8016 KolejnÄ funkcjonalnoÅciÄ w usÅudze NMT jest możliwoÅÄ wyznaczania minimalnej 8017 i maksymalnej wysokoÅci w obszarze, a także lokalizacjÄ punktów charakteryzujÄ cych siÄ 8018 tymi ekstremalnymi wysokoÅciami. PrzykÅadowe zapytanie zwiÄ zane z opisywanÄ 8019 funkcjonalnoÅciÄ ma postaÄ: 8020 https://services.gugik.gov.pl/nmt/?request=GetMinMaxByPolygon&polygon=POLYGON((563800 8021 243490,563950 243490,563950 243400,563800 243400,563800 243490))&json 8022 W wyniku zapytania otrzymujemy minimalnÄ i maksymalnÄ wysokoÅÄ w zadanym 8023 obszarze oraz dwie listy punktów - jednÄ z wysokoÅciami minimalnymi, a drugÄ 8024 z wysokoÅciami maksymalnymi (rys. 52). 8025 */ 8026 8027 getGugikMinMaxByPolygon: async (lat, lon, size = 80) => { 8028 proj4.defs("EPSG:2180","+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); 8029 8030 let pl1992 = proj4("EPSG:2180").forward([+lon,+lat]); 8031 const point = { latitude: lat, longitude: lon, y: Math.round(pl1992[1]), x: Math.round(pl1992[0]) }; 8032 const x1 = Math.round(point.x - size/2); 8033 const x2 = Math.round(point.x + size/2); 8034 const y1 = Math.round(point.y - size/2); 8035 const y2 = Math.round(point.y + size/2); 8036 const polygon = `POLYGON((${x1} ${y2},${x2} ${y2},${x2} ${y1},${x1} ${y1},${x1} ${y2}))`; 8037 console.log(point); 8038 console.log(polygon); 8039 8040 const url = 'https://services.gugik.gov.pl/nmt/?' + makeParams({ 8041 request: 'GetMinMaxByPolygon', 8042 polygon: polygon 8043 }) + '&json'; 8044 console.log(url); 8045// https://services.gugik.gov.pl/nmt/?request=GetMinMaxByPolygon&polygon=POLYGON((563800 243490,563950 243490,563950 243400,563800 243400,563800 243490))&json 8046 try { 8047 const response = await fetch(url); 8048 if (!response.ok) { 8049 throw new Error(`API error: ${response.status}`); 8050 } 8051 const data = await response.json(); 8052 console.log(data); 8053 let wkt = new Wkt.Wkt(); 8054 wkt.read(data['Hmax geom'][0]); 8055 const wktObj = wkt.toObject()._latlng; 8056 const wgs = proj4("EPSG:2180").inverse([wktObj.lng,wktObj.lat]); 8057 const ret = { data: data, maxPoint: { lat: wgs[1], lon: wgs[0], elevation: +data.Hmax } }; 8058 console.log(ret); 8059 console.log(`lat="${ret.maxPoint.lat.toFixed(5)}" lon="${ret.maxPoint.lon.toFixed(5)}" source="gugik-refine" elevation="${Math.round(ret.maxPoint.elevation)}"`); 8060 return ret; 8061 } catch (error) { 8062 console.error('Error fetching min/max:', error); 8063 throw error; 8064 return null; 8065 } 8066 8067 }, 8068 8069 getGugikObjectsFromBounds: async (lat1, lon1, lat2, lon2) => { 8070 const response = await fetch(`https://mapy.geoportal.gov.pl/wss/service/wfsGN/guest?SERVICE=WFS&REQUEST=GetFeature&bbox=${lat1},${lon1},${lat2},${lon2}&VERSION=2.0.0&TYPENAMES=gn:NamedPlace&TYPENAME=gn:NamedPlace&outputFormat=application/json`); 8071 const geoJsonData = await response.json(); 8072 return geoJsonData?.features?.filter( f => f.type=='Feature' && f.geometry.coordinates.length==1 && f.properties.localType?.LocalisedCharacterString=='obUksztaltowaniaTerenu' ).map(f => { 8073 const ret = { 8074 name: f.properties.name[0].spelling.SpellingOfName.text, 8075 lat: f.geometry.coordinates[0][1], 8076 lon: f.geometry.coordinates[0][0] 8077 } 8078 if (f.properties.name.length>1) { 8079 ret.names = f.properties.name.map( n => n.spelling.SpellingOfName.text ); 8080 } 8081 return ret; 8082 }); 8083 }, 8084 8085 getCountryFromCoordinates: async (lat, lon) => { 8086 const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=3&addressdetails=1`; 8087 8088 try { 8089 const response = await fetch(url, { 8090 headers: { 8091 "User-Agent": giga.userAgentString 8092 } 8093 }); 8094 const data = await response.json(); 8095 8096 const country = data.address?.country; 8097 const countryCode = data.address?.country_code?.toUpperCase(); 8098 8099 console.log(`Country: ${country} (${countryCode})`); 8100 return { country, countryCode }; 8101 } catch (error) { 8102 console.error("Error fetching country from Nominatim:", error); 8103 return null; 8104 } 8105 }, 8106 8107 defineCoordTransformations: () => { 8108 proj4.defs("EPSG:5514:CZ", "+proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=570.8,85.7,462.8,4.998,1.587,5.261,3.56 +units=m +no_defs"); 8109 proj4.defs("EPSG:5514:SK", "+proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +units=m +no_defs"); 8110 } 8111 8112 8113}; 8114 8115 8116$(function() { 8117 8118 giga.compass.create('#compass-container'); 8119 8120 /* 8121 $(window).on('resize', function() { 8122 giga.resizeHandler(); 8123 }); 8124 8125 */ 8126 8127 document.body.addEventListener('click', function(event) { 8128 const target = event.target.closest('.hs'); 8129 if (target) { 8130 giga.moveToHotspot(target.dataset.name); 8131 } 8132 }); 8133 document.body.addEventListener('click', function(event) { 8134 const el = event.target.closest('.tag'); 8135 if (!el) return; 8136 8137 const tag = el.dataset.tag; 8138 8139 Array.from(el.parentElement.children) 8140 .filter(sibling => sibling !== el && sibling.classList.contains('tag')) 8141 .forEach(sibling => sibling.classList.remove('active')); 8142 8143 el.classList.add('active'); 8144 8145 const resultsContainer = document.getElementById(el.dataset.resultsContainer); 8146 resultsContainer.innerHTML = '<div class="infobox-section no-top-border see-also">' + 8147 hotspots.findPanoramasByTag(tag).map(e => '<div>' + giga.makePanoramaLink(hotspots.panoramas[e].title, e) + '</div>').join(' ') + '</div>'; 8148 resultsContainer.style.display = ''; 8149 }); 8150 8151 $('body').on('click', '.zoom-to-range', function() { 8152 const limits = giga.getRangeLimits( h => hotspots.points.get(h.id)?.range==$(this).text()?.trim() ); 8153 const fov = limits.right - limits.left; 8154 const dirH = limits.right - fov/2; 8155 const dirV = limits.top - (limits.top-limits.bottom)/2; 8156 const lookto = `lookto(${dirH},${dirV})`; 8157 k.view.hfov = fov * 1.1; //add horizontal 10% margin 8158 k.call(lookto); 8159 }); 8160 8161 $('body').on('click', 'a.reload', function() { 8162 window.location.assign($(this).attr('href')); 8163 window.location.reload(true); 8164 }); 8165 8166 $('body').on('click', 'a.panorama-hotspot-link', function(event) { 8167 const $this = $(this); 8168 if (!!$this.data('panorama')) { 8169 event.preventDefault(); 8170 giga.loadPanorama($this.data('panorama'),$this.data('ath'),$this.data('atv'),$this.data('zoom')); 8171 } 8172 }); 8173 8174 $('body').on('click', 'a.panorama-hotspot-link-mini', function(event) { 8175 const $this = $(this); 8176 //console.log($this); 8177 if (!!$this.data('panorama')) { 8178 event.preventDefault(); 8179 giga.loadMiniPanorama($this.data('panorama'),$this.data('ath'),$this.data('atv'),$this.data('zoom')*0.5); 8180 } 8181 }); 8182 8183 $('html').on('keydown', function(e) {
8184 if (e.altKey && (e.which==49 || e.which==50 || e.which==51 || e.which==52)) { 8185 $('#panel-navi .panel-tab-button')[e.which-49].click(); 8186 } else if (e.key=='F2') { 8187 if (document.querySelector('input#label-narrow')?.checkVisibility()) { 8188 document.querySelector('input#label-narrow').click(); 8189 } 8190 } else if (e.key=='F4') { 8191 if (document.querySelector('input#label-acronym')?.checkVisibility()) { 8192 document.querySelector('input#label-acronym').click(); 8193 } 8194 } else { 8195 //console.log(e); 8196 } 8197 }); 8198 8199 $('#addhotspot').on('click', '#send.active', function() { 8200 if ($('#hsname').val()) { 8201 $('#addhotspot #send').removeClass('active').html('...czekaj...'); 8202 const info = { 8203 panoid: k.state.currentpanorama, 8204 ath: $('#newH').text(), 8205 atv: $('#newV').text(), 8206 text: $('#hsname').val(), 8207 title: $('#hstitle').val(), 8208 level: giga.getCurrentViewParameters().level, 8209 narrow: $('#label-narrow').prop('checked') ? 1 : 0, 8210 acronym: $('#label-acronym').prop('checked') ? 1 : 0, 8211 description: $('#hsdesc').val() 8212 }; 8213 giga.removeNewHotspot(); 8214 $.post("add-hotspot.html", info, function(data) { 8215 $('#addhotspot #send').html('OK'); 8216 $('#addhotspot').fadeOut('slow'); 8217 }); 8218 } 8219 }); 8220 8221 $('#addhotspot').on('click', '.suggested', function(ev) { 8222 const id = $(this).data('id'); 8223 const point = hotspots.points.get(id); 8224 $('input#hsname').val(id).trigger('input'); 8225 $('input#hstitle').val(point.title); 8226 giga.showPointInfo(id); 8227 if (ev.ctrlKey) { 8228 const info = k.info, lat = point.lat, lon = point.lon; 8229 giga.mapycom.showMap(lat, lon, point.name, { 8230 zoom: 15, 8231 lines: [ 8232 [ {lat: +info.lat, lon: +info.lon}, {lat: lat, lon: lon} ] 8233 ] 8234 }).flyToWithLine(lat, lon, +info.lat, +info.lon); 8235 } else if (ev.altKey && k.info.elevation) { 8236 giga.lineOfSight.checkFromCurrent(point); 8237 } else if (ev.altKey) { 8238 showScreenMessage('nieznana wysokoÅÄ punktu poczÄ tkowego'); 8239 } 8240 }); 8241 8242 $('body').on('click','.popup .close', function() { 8243 $(this).parents('.popup').hide(); 8244 $('#krpano').focus(); 8245 }); 8246 8247 $('#addhotspot').on('click','.close', function() { 8248 giga.removeNewHotspot(); 8249 }); 8250 8251 $('#overlay-map').on('click','.close', function() { 8252 giga.currentPanorama.overlayMap.hide(); 8253 //$('#krpano').focus(); 8254 }); 8255 8256 document.getElementById('panobox')?.addEventListener('click', ev => { 8257 const el = ev.target.closest('.list-all-objects'); 8258 if (!el) return; 8259 8260 const sortby = el.dataset.sortby; 8261 let stats = ''; 8262 if (giga.advanced) stats = giga.currentPanorama.formatObjectsStats(giga.currentPanorama.makeObjectsStats()) + '<br>'; 8263 8264 document.getElementById('objects-list')?.remove(); 8265 8266 const div = document.createElement('div'); 8267 div.className = 'infobox-section'; 8268 div.id = 'objects-list'; 8269 div.innerHTML = `<h2>Lista wszystkich</h2>${stats}${giga.currentPanorama.makeObjectsList(sortby)}`; 8270 document.getElementById('panobox').appendChild(div); 8271 }); 8272 8273 $('#infobox').on('click','.close', function() { 8274 $('#infobox').data('hs',''); 8275 }); 8276 8277 document.body.addEventListener('click', ev => { 8278 const el = ev.target.closest('.download-line-kml'); 8279 if (!el) return; 8280 const azimuth = el.dataset.azimuth || giga.hposToAzimuth(k.view.hlookat); 8281 giga.downloadViewLineKML(+azimuth, 120000); 8282 }); 8283 8284 document.body.addEventListener('click', ev => { 8285 const el = ev.target.closest('.download-point-kml'); 8286 if (!el) return; 8287 8288 const { lat, lon, name } = el.dataset; 8289 8290 if (ev.ctrlKey) { 8291 window.open('/giga/coord.html?' + makeParams({ lat, lon, title: name })); 8292 } else if (!ev.altKey) { 8293 giga.downloadPointKML(name, lat, lon); 8294 } 8295 }); 8296 8297 $('body').on('click','.open-mapy-cz', function(ev) { 8298 const $this = $(this), info = k.info, lat = +$this.data('lat'), lon = +$this.data('lon'); 8299 giga.mapycom.showMap(lat, lon, $this.data('name'), { 8300 zoom: 15, 8301 lines: [ 8302 [ {lat: +info.lat, lon: +info.lon}, {lat: lat, lon: lon} ] 8303 ] 8304 }).flyTo(lat, lon); 8305 }); 8306 8307 $('body').on('click','.open-mapy-cz-line', function(ev) { 8308 const $this = $(this), info = k.info, lat = +$this.data('lat'), lon = +$this.data('lon'); 8309 giga.mapycom.showMap(lat, lon, $this.data('name'), { 8310 zoom: 15, 8311 lines: [ 8312 [ {lat: +info.lat, lon: +info.lon}, {lat: lat, lon: lon} ] 8313 ] 8314 }).flyToWithLine(lat, lon, +info.lat, +info.lon); 8315 }); 8316 8317 document.body.addEventListener('click', ev => { 8318 const el = ev.target.closest('.clear-points-storage'); 8319 if (!el) return; 8320 8321 el.style.transition = 'opacity 0.6s'; 8322 el.style.opacity = '0'; 8323 el.addEventListener('transitionend', () => el.style.display = 'none', { once: true }); 8324 hotspots.clearPointsStorage(); 8325 giga.sw.clearCache(); 8326 }); 8327 8328 const CLICK_ACTIONS = { 8329 'toggle-overlay-map': () =>
8329 giga.mapycom.toggle(), 8330 'generate-current-view-ude': () => giga.generateCurrentViewUde(), 8331 'advanced-mode-toggle': () => giga.toggleAdvancedMode(), 8332 'generate-pttk-code': () => giga.clipboard.copy(giga.currentPanorama.generatePTTKCode()), 8333 'enable-vertical-angle-optimizer': () => giga.peakFinder.showOptimizer(), 8334 'copy': el => giga.clipboard.copy(el.dataset.copy), 8335 'level-symbol': el => giga.clipboard.copy(`id="${el.title}"`), 8336 'nmt-refine': el => giga.openNMTRefine(el.dataset.lat, el.dataset.lon, el.dataset.title), 8337 'swiss-refine': el => giga.openSwissRefine(el.dataset.lat, el.dataset.lon, el.dataset.title), 8338 'zbgis-refine': el => giga.openZbgisRefine(el.dataset.lat, el.dataset.lon, el.dataset.title), 8339 'cuzk-refine': el => giga.openCuzkRefine(el.dataset.lat, el.dataset.lon, el.dataset.title), 8340 }; 8341 8342 document.body.addEventListener('click', ev => { 8343 // alt-copy handled separately due to modifier key requirement 8344 if (ev.altKey && ev.target.closest('.alt-copy')) { 8345 giga.clipboard.copy(ev.target.closest('.alt-copy').dataset.copy); 8346 return; 8347 } 8348 8349 const selector = Object.keys(CLICK_ACTIONS).join(',.'); 8350 const el = ev.target.closest(`.${selector}`); 8351 if (!el) return; 8352 8353 const action = Object.keys(CLICK_ACTIONS).find(cls => el.classList.contains(cls)); 8354 if (action) CLICK_ACTIONS[action](el); 8355 }); 8356 8357 const draggables = $(".draggable"); 8358 if (draggables.length>0 && 'draggable' in draggables) draggables.draggable({ 8359 cursor: 'move', 8360 cancel: '.no-drag, input, textarea, button, select, option', 8361 scroll: false 8362 }); 8363 8364 const resizables = $(".resizable"); 8365 if (resizables.length>0 && 'resizable' in resizables) resizables.resizable({ 8366 handles: 'n, e, s, w, se, sw, nw', 8367 autoHide: true, 8368 cancel: ".no-resize", 8369 minWidth: 120, 8370 minHeight: 80 8371 }); 8372 8373 $('#side-panel .handle').data('hidden',false); 8374 $('#side-panel .handle').on('click',function() { 8375 if ($(this).data('hidden')) { 8376 $(this).data('hidden',false); 8377 $('#side-panel').removeClass('panel-hidden'); 8378 $('#side-panel .handle div').html('»'); 8379 $('#pano').removeClass('panel-hidden'); 8380 } else { 8381 $(this).data('hidden',true); 8382 $('#side-panel').addClass('panel-hidden'); 8383 $('#side-panel .handle div').html('«'); 8384 $('#pano').addClass('panel-hidden'); 8385 } 8386 }); 8387 8388 $('#side-panel').on('click', '.other-panoramas h3', function() { 8389 giga.sortOtherPanoramas('panorama',false); 8390 }); 8391 8392 window.setTimeout( function() { 8393 document.getElementById('krpano')?.focus(); 8394 try { 8395 k.focus(); 8396 } catch(e) { 8397 } 8398 window.activekrpanowindow='krpano'; 8399 }, 90); 8400 8401}); 8402 8403 8404 8405// some helpers 8406 8407function ifset(o,out) { 8408 out = typeof(out)==='undefined' ? '' : out; 8409 return typeof(o)==='undefined' || o===null || o=='' ? out : o; 8410} 8411 8412function hasValue(...a) { 8413 return a.every( x => typeof(x)!=='undefined' && x!=='' ); 8414} 8415 8416function isNumeric(obj) { 8417 // As of jQuery 3.0, isNumeric is limited to 8418 // strings and numbers (primitives or objects) 8419 // that can be coerced to finite numbers (gh-2662) 8420 // parseFloat NaNs numeric-cast false positives ("") 8421 // ...but misinterprets leading-number strings, particularly hex literals ("0x...") 8422 // subtraction forces infinities to NaN 8423 return ( typeof(obj)==="number" || typeof(obj)==="string" ) && !isNaN( obj - parseFloat(obj) ); 8424} 8425 8426function odmien(x,t1,t2,t3) { 8427 if (x==1) return t1; 8428 x = x % 100; 8429 const j = x % 10; 8430 const d = Math.floor(x/10); 8431 if (j>=2 && j<=4 && d!=1) return t2; 8432 return t3; 8433} 8434 8435function addSign(n) { 8436 return n>0 ? ('+'+n) : n; 8437} 8438 8439function makeAttr(s) { 8440 return s.replace(/\"/g,'"'); 8441} 8442 8443function makeParams(params) { 8444 return (new URLSearchParams(params)).toString(); 8445} 8446 8447function transliterate(s) { 8448 return /[а-ÑÐ-ЯÐÑ]/.test(s) ? cyrillicToTranslit({ preset: 'uk' }).transform(s,' ') : s; 8449} 8450 8451function median(arr1) { 8452 const arr = arr1.slice().sort((a, b) => a - b); 8453 const mid = arr.length >> 1; 8454 return arr.length & 1 ? arr[mid] : (arr[mid - 1] + arr[mid]) / 2; 8455} 8456 8457function mean(arr) { 8458 let sum = 0; 8459 for (let i = 0; i < arr.length; i++) sum += arr[i]; 8460 return sum / arr.length; 8461} 8462 8463function kebabCase(s) { 8464 return s.normalize("NFKD").replace(/\p{Diacritic}/gu, "").replace('Å', 'l').replace(/[\s_]+/g, '-').toLowerCase(); 8465} 8466 8467const XML_ESCAPE = { '&': '&', '"': '"', "'": ''', '<': '<', '>': '>' }; 8468function escapeXML(s) { 8469 return s.replace(/[&"'<>]/g, c => XML_ESCAPE[c]); 8470} 8471 8472function dmsCoords(lat,lon) { 8473 return formatcoords(+lat,+lon).format('DDMMss X', {latLonSeparator: ' ', decimalPlaces: 1}); 8474} 8475 8476const ISO_COUNTRIES = { 8477 PL: 'Polska', SK: 'SÅowacja', CZ: 'Czechy', 8478 AT: 'Austria', DE: 'Niemcy', LI: 'Liechtenstein', 8479 CH: 'Szwajcaria', FR: 'Francja', IT: 'WÅochy', 8480 UA: 'Ukraina', HU: 'WÄgry', RO: 'Rumunia', 8481 HR: 'Chorwacja', SI: 'SÅowenia', ES: 'Hiszpania', 8482}; 8483
8484function ISOCountryCode(code) { 8485 return ISO_COUNTRIES[code] ?? '???'; 8486} 8487 8488function getScaling(zoomedOut,zoomedIn,precision,steps) { 8489 steps = steps || 10; 8490 precision = precision || 4; 8491 const factor = Math.pow(zoomedOut/zoomedIn, 1/(steps-1)); 8492 var levels = []; 8493 for (let i=0;i<steps;i++) { 8494 levels.push(+(zoomedOut/Math.pow(factor,i)).toFixed(precision)); 8495 } 8496 return levels; 8497} 8498 8499function getScalingToPrint(zoomedOut,zoomedIn,precision,steps) { 8500 return getScaling(zoomedOut,zoomedIn,precision,steps).map((x,i) => `<group name="level${i}" fov="${x}" />`).join('\n'); 8501} 8502 8503function storageAvailable(type) { 8504 try { 8505 var storage = window[type], 8506 x = '__storage_test__'; 8507 storage.setItem(x, x); 8508 storage.removeItem(x); 8509 return true; 8510 } catch(e) { 8511 return e instanceof DOMException && ( 8512 // everything except Firefox 8513 (e.code === 22 || 8514 // Firefox 8515 e.code === 1014 || 8516 // test name field too, because code might not be present 8517 // everything except Firefox 8518 e.name === 'QuotaExceededError' || // Firefox 8519 e.name === 'NS_ERROR_DOM_QUOTA_REACHED')) && 8520 // acknowledge QuotaExceededError only if there's something already stored 8521 storage.length !== 0; 8522 } 8523} 8524 8525 8526// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math/round 8527 8528(function() { 8529 /** 8530 * Decimal adjustment of a number. 8531 * 8532 * @param {String} type The type of adjustment. 8533 * @param {Number} value The number. 8534 * @param {Integer} exp The exponent (the 10 logarithm of the adjustment base). 8535 * @returns {Number} The adjusted value. 8536 */ 8537 function decimalAdjust(type, value, exp) { 8538 // If the exp is undefined or zero... 8539 if (typeof exp === 'undefined' || +exp === 0) { 8540 return Math[type](value); 8541 } 8542 value = +value; 8543 exp = +exp; 8544 // If the value is not a number or the exp is not an integer... 8545 if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { 8546 return NaN; 8547 } 8548 // If the value is negative... 8549 if (value < 0) { 8550 return -decimalAdjust(type, -value, exp); 8551 } 8552 // Shift 8553 value = value.toString().split('e'); 8554 value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); 8555 // Shift back 8556 value = value.toString().split('e'); 8557 return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); 8558 } 8559 8560 // Decimal round 8561 if (!Math.round10) { 8562 Math.round10 = function(value, exp) { 8563 return decimalAdjust('round', value, exp); 8564 }; 8565 } 8566 // Decimal floor 8567 if (!Math.floor10) { 8568 Math.floor10 = function(value, exp) { 8569 return decimalAdjust('floor', value, exp); 8570 }; 8571 } 8572 // Decimal ceil 8573 if (!Math.ceil10) { 8574 Math.ceil10 = function(value, exp) { 8575 return decimalAdjust('ceil', value, exp); 8576 }; 8577 } 8578})(); 8579 8580 8581// Vanilla JS truncate utility 8582function truncateHTML(html, options = {}) { 8583 const defaults = { 8584 stripTags: false, 8585 words: false, 8586 keepFirstWord: false, 8587 noBreaks: false, 8588 finishBlock: false, 8589 length: Infinity, 8590 ellipsis: 'â¦' 8591 }; 8592 8593 const o = Object.assign({}, defaults, options); 8594 8595 // Build DOM 8596 const wrapper = document.createElement('div'); 8597 wrapper.innerHTML = html; 8598 8599 if (o.noBreaks) { 8600 wrapper.querySelectorAll('br').forEach(br => br.replaceWith(' ')); 8601 } 8602 8603 if (o.stripTags) { 8604 wrapper.textContent = wrapper.textContent; // strip all tags 8605 } 8606 8607 let fullText = wrapper.textContent; 8608 if (fullText.length <= o.length) { 8609 return wrapper.innerHTML; 8610 } 8611 8612 // Wordâsafe logic 8613 let cut = o.length; 8614 if (o.words) { 8615 const chop = /(\s*\S+|\s)$/; 8616 const start = /^(\S*)/; 8617 8618 const truncated = fullText.slice(0, o.length).replace(chop, '').length; 8619 8620 if (o.keepFirstWord && truncated === 0) { 8621 cut = start.exec(fullText)[0].length; 8622 } else { 8623 cut = truncated; 8624 } 8625 } 8626 8627 // Recursive DOM trimming 8628 function trimNode(node, remaining) { 8629 const text = node.textContent; 8630 const len = text.length; 8631 8632 if (len <= remaining) { 8633 return remaining - len; 8634 } 8635 8636 // Text node 8637 if (node.nodeType === Node.TEXT_NODE) { 8638 const cutPos = o.finishBlock ? len : (len - remaining - 1); 8639 const remainder = node.splitText(cutPos); 8640 remainder.replaceWith(o.ellipsis); 8641 return 0; 8642 } 8643 8644 // Element node 8645 const children = Array.from(node.childNodes).reverse(); 8646 for (const child of children) { 8647 remaining = trimNode(child, remaining); 8648 if (remaining <= 0) break; 8649 } 8650 return remaining; 8651 } 8652 8653 trimNode(wrapper, fullText.length - cut); 8654 8655 return wrapper.innerHTML; 8656} 8657 8658 8659function fixedEncodeURIComponent(str) { 8660 return encodeURIComponent(str).replace( 8661 /[!'()*]/g, 8662 (c) =>
8662 `%${c.charCodeAt(0).toString(16).toUpperCase()}` 8663 ); 8664} 8665 8666function interpolatingPolynomial(points) { 8667 var n = points.length - 1, p; 8668 8669 p = function (i, j, x) { 8670 if (i === j) return points[i][1]; 8671 return ((points[j][0] - x) * p(i, j - 1, x) + (x - points[i][0]) * p(i + 1, j, x)) / (points[j][0] - points[i][0]); 8672 }; 8673 8674 return function (x) { 8675 if (points.length === 0) return 0; 8676 return p(0, n, x); 8677 }; 8678}; 8679 8680function dataURItoBlob(dataURI) { 8681 // convert base64 to raw binary data held in a string 8682 // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this 8683 const byteString = atob(dataURI.split(',')[1]); 8684 8685 // separate out the mime component 8686 const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] 8687 8688 // write the bytes of the string to an ArrayBuffer 8689 const ab = new ArrayBuffer(byteString.length); 8690 8691 // create a view into the buffer 8692 var ia = new Uint8Array(ab); 8693 8694 // set the bytes of the buffer to the correct values 8695 for (let i = 0; i < byteString.length; i++) { 8696 ia[i] = byteString.charCodeAt(i); 8697 } 8698 8699 // write the ArrayBuffer to a blob, and you're done 8700 const blob = new Blob([ab], {type: mimeString}); 8701 return blob; 8702} 8703 8704const animateCSS = (element, animation, prefix = 'animate__') => { 8705 // We create a Promise and return it 8706 new Promise((resolve, reject) => { 8707 const animationName = `${prefix}${animation}`; 8708 const node = typeof element == 'string' ? document.querySelector(element) : element; 8709 console.log(node); 8710 node.classList.add(`${prefix}animated`, animationName); 8711 // When the animation ends, we clean the classes and resolve the Promise 8712 function handleAnimationEnd(event) { 8713 event.stopPropagation(); 8714 node.classList.remove(`${prefix}animated`, animationName); 8715 resolve('Animation ended'); 8716 } 8717 node.addEventListener('animationend', handleAnimationEnd, {once: true}); 8718 }); 8719}; 8720 8721function parseWikiLangString (input) { 8722 if (typeof input !== 'string') return { lang: '', text: '' }; 8723 8724 const parts = input.split(':'); 8725 if (parts.length < 2 || !parts[0] || !parts[1]) { 8726 return { 8727 lang: '', text: input 8728 }; // fallback: treat whole string as text 8729 } 8730 8731 return { 8732 lang: parts[0].trim(), 8733 text: parts.slice(1).join(':').trim() // handles multiple colons 8734 }; 8735} 8736 8737const loadScript = (url, async = true, type = "text/javascript") => { 8738 return new Promise((resolve, reject) => { 8739 try { 8740 const scriptEle = document.createElement("script"); 8741 scriptEle.type = type; 8742 scriptEle.async = async; 8743 scriptEle.src = url; 8744 8745 scriptEle.addEventListener("load", function(ev) { 8746 resolve({ status: true }); 8747 }); 8748 8749 scriptEle.addEventListener("error", function(ev) { 8750 reject({ 8751 status: false, 8752 message: `Failed to load the script ï¼{url}` 8753 }); 8754 }); 8755 8756 document.body.appendChild(scriptEle); 8757 } catch (error) { 8758 reject(error); 8759 } 8760 }); 8761}; 8762 8763/* 8764loadScript("file1_url") 8765 .then( data => { 8766 console.log("Script loaded successfully", data); 8767 }) 8768 .catch( err => { 8769 console.error(err); 8770 }); 8771*/ 8772 8773const findDuplicates = function (obj) { 8774 const z = obj instanceof Array ? obj : Array.from(obj); 8775 return z.reduce((acc, v, i, arr) => arr.indexOf(v) !== i && acc.indexOf(v) === -1 ? acc.concat(v) : acc, []); 8776} 8777//findDuplicates(Array.from(hotspots.points.values()).filter(p=>!!p.mapycz).map(p=>p.mapycz)); 8778 8779 8780const findDuplicatePoints = (arr, precision) => { 8781 const frequency = new Map(); 8782 const duplicates = []; 8783 8784 const makeKey = (p, precision) => { 8785 if (Number.isInteger(precision)) { 8786 return {lat: p.lat.toFixed(precision), lon: p.lon.toFixed(precision)}; 8787 } else { 8788 return {lat: p.lat, lon: p.lon}; 8789 } 8790 } 8791 8792 arr.forEach(point => { 8793 const key = JSON.stringify(makeKey(point, precision)); 8794 const val = (frequency.get(key) || []); 8795 val.push({id: point.id, lat: point.lat, lon: point.lon}); 8796 frequency.set(key, val); 8797 }); 8798 8799 frequency.forEach((value, key) => { 8800 if (value.length > 1) duplicates.push(value); 8801 }); 8802 8803 return duplicates; 8804} 8805 8806function isObjectEmpty(obj) { 8807 for (const prop in obj) { 8808 if (Object.hasOwn(obj, prop)) return false;
8809 } 8810 return true; 8811} 8812 8813const getHotspotsFrequency = () => { 8814 const doCount = () => { 8815 const mm = new Map(); 8816 panos.forEach(pan => giga.data[pan].toArray().forEach(hs => mm.set(hs.id, 1 + (mm.get(hs.id) || 0)))); 8817 return Array.from(mm).sort((a,b) => b[1]-a[1]); 8818 } 8819 if (isObjectEmpty(giga.data)) { 8820 return giga.loadData(panos).then(doCount); 8821 } else { 8822 return Promise.resolve(doCount()); 8823 } 8824} 8825// .....filter(el=>hotspots.points.get(el[0])?.mapycz).forEach(el=>console.log(el)); 8826 8827const measurePerformance = (func, iterations=1, description='Measured') => { 8828 const startTime = performance.now(); 8829 for (let i=1;i<=iterations;i++) func(); 8830 const endTime = performance.now(); 8831 console.log(`${description}: ${(endTime-startTime).toFixed(5)} ms`) 8832} 8833 8834if (!('getScreenDetails' in window)) { 8835 window.getScreenDetails = async () => [window.screen]; 8836 window.screen.isExtended = false; 8837} 8838function makeElement (tag='div', html='') { 8839 const el = document.createElement(tag); 8840 if (html) el.innerHTML = html; 8841 return el; 8842} 8843function chunkArray(arr, chunkSize) { 8844 let list = [...arr]; 8845 return [...Array(Math.ceil(list.length / chunkSize))].map(() => list.splice(0, chunkSize)); 8846} 8847function sleep(ms) { 8848 return new Promise(resolve => setTimeout(resolve, ms)); 8849} 8850 8851function showScreenMessage(text, options={}) { 8852 const { 8853 severity = '', 8854 category = '', 8855 duration = 900 8856 } = options; 8857 const containerId = 'screen-message-container'; 8858 let container = document.getElementById(containerId); 8859 8860 // Create container if it doesn't exist 8861 if (!container) { 8862 const panoContainer = document.getElementById('pano'); 8863 if (!panoContainer) return; 8864 container = document.createElement('div'); 8865 container.id = containerId; 8866 panoContainer.appendChild(container); 8867 } 8868 8869 function scheduleMessageTimeout(msg, duration) { 8870 return setTimeout(() => { 8871 msg.style.opacity = '0'; 8872 msg.addEventListener('transitionend', () => { 8873 msg.remove(); 8874 // If no messages left, remove container 8875 if (container.children.length === 0) { 8876 //container.remove(); 8877 } 8878 }, { once: true }); 8879 }, duration); 8880 } 8881 8882 while (container.children.length >= 6) { 8883 container.children[0].remove(); 8884 } 8885 if (category) { 8886 let lastMessage = container.children[container.children.length-1]; 8887 if (lastMessage && category==lastMessage.dataset.category) { 8888 clearTimeout(Number(lastMessage.dataset.messageTimeout)); 8889 lastMessage.textContent = text; 8890 lastMessage.dataset.messageTimeout = scheduleMessageTimeout(lastMessage, duration); 8891 return; 8892 } 8893 } 8894 8895 const msg = document.createElement('div'); 8896 msg.textContent = text; 8897 msg.classList.add('screen-message'); 8898 if (category) msg.dataset.category = category; 8899 if (severity) msg.dataset.severity = severity; 8900 container.appendChild(msg); 8901 8902 // Trigger fade-in 8903 requestAnimationFrame(() => { 8904 msg.style.opacity = '1'; 8905 msg.dataset.messageTimeout = scheduleMessageTimeout(msg, duration); 8906 }); 8907 8908} 8909 8910// simulateKey(document, "keydown", "Enter", { code: "Enter" }); 8911function simulateKey(target, type, key, options = {}) { 8912 const event = new KeyboardEvent(type, { 8913 key, 8914 code: options.code || key, 8915 keyCode: key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0, 8916 which: key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0, 8917 bubbles: true, 8918 cancelable: true, 8919 ...options 8920 }); 8921 8922 // Some browsers ignore keyCode/which unless forced 8923 Object.defineProperty(event, "keyCode", { get: () => event.keyCode }); 8924 Object.defineProperty(event, "which", { get: () => event.which }); 8925 8926 target.dispatchEvent(event); 8927} 8928function simulateKeyDown(key) { 8929 simulateKey(document, 'keydown', key) ; 8930} 8931 8932class Dropdown { 8933 /* 8934 * @param {HTMLElement} triggerEl - The element that shows the dropdown 8935 * @param {Array<{label:string,value:any}>} items - Array of objects with label and value 8936 * @param {Function} callback - Function called with the selected value 8937 * @param {Object} options - {mode: "click"|"hover"} 8938 */ 8939 constructor(triggerEl, items, callback, options = {mode: "click"}) { 8940 if (!triggerEl) { 8941 console.error('no trigger element for dropdown'); 8942 return; 8943 } 8944 this.triggerEl = triggerEl; 8945 this.items = items; 8946 this.callback = callback; 8947 this.dropdown = null; 8948 this.mode = options.mode; 8949 8950 if (this.mode === "click") { 8951 this.triggerEl.addEventListener("click", () => { 8952 this.dropdown ? this.close() : this.open(); 8953 }); 8954 } else if (this.mode === "hover") { 8955 this.triggerEl.addEventListener("mouseenter", () => this.open()); 8956 this.triggerEl.addEventListener("mouseleave", () => this.closeDelayed()); 8957 } 8958 } 8959 8960 open() { 8961 this.close(); // ensure clean state 8962 8963 this.dropdown = document.createElement("div"); 8964 this.dropdown.className = "dropdown"; 8965
8966 this.items.forEach(itemData => { 8967 const item = document.createElement("div"); 8968 item.textContent = itemData.label; 8969 item.addEventListener("click", () => { 8970 this.close(); 8971 this.callback(itemData.value); 8972 }); 8973 this.dropdown.appendChild(item); 8974 }); 8975 8976 document.body.appendChild(this.dropdown); 8977 8978 const rect = this.triggerEl.getBoundingClientRect(); 8979 this.dropdown.style.left = rect.left + "px"; 8980 this.dropdown.style.top = rect.bottom + "px"; 8981 8982 if (this.mode === "hover") { 8983 this.dropdown.addEventListener("mouseenter", () => this.cancelClose()); 8984 this.dropdown.addEventListener("mouseleave", () => this.closeDelayed()); 8985 } 8986 8987 document.addEventListener("click", this.outsideClickHandler); 8988 document.addEventListener("keydown", this.escHandler); 8989 } 8990 8991 close() { 8992 if (this.dropdown) { 8993 this.dropdown.remove(); 8994 this.dropdown = null; 8995 8996 document.removeEventListener("click", this.outsideClickHandler); 8997 document.removeEventListener("keydown", this.escHandler); 8998 } 8999 } 9000 9001 closeDelayed() { 9002 this.closeTimer = setTimeout(() => this.close(), 200); 9003 } 9004 9005 cancelClose() { 9006 if (this.closeTimer) { 9007 clearTimeout(this.closeTimer); 9008 this.closeTimer = null; 9009 } 9010 } 9011 9012 outsideClickHandler = (e) => { 9013 if (this.dropdown && !this.dropdown.contains(e.target) && e.target !== this.triggerEl) { 9014 this.close(); 9015 } 9016 } 9017 9018 escHandler = (e) => { 9019 if (e.key === "Escape") { 9020 this.close(); 9021 } 9022 } 9023} 9024 9025function getCssRule(selector) { 9026 for (const sheet of document.styleSheets) { 9027 try { 9028 for (const rule of sheet.cssRules) { 9029 if (rule.type === CSSRule.STYLE_RULE && 9030 rule.selectorText === selector) { 9031 return rule; 9032 } 9033 } 9034 } catch (e) { 9035 // ignore cross-origin stylesheets 9036 } 9037 } 9038 return null; 9039} 9040 9041 /** 9042 * Attach wheel-direction listener to an element. 9043 * @param {Element} element 9044 * @param {(direction: 'up' | 'down', e: WheelEvent) => void} callback 9045 * @param {Object} [options] 9046 * @param {boolean} [options.preventDefault=true] - block page scroll 9047 * @param {number} [options.threshold=0] - ignore deltas smaller than this 9048 * @returns {() => void} cleanup function to remove the listener 9049 9050// usage 9051const removeListener = onWheelDirection( 9052 document.getElementById('myElement'), 9053 (direction, e) => console.log('scrolled', direction) 9054); 9055 9056// later, to detach: 9057// removeListener(); 9058 */ 9059function onWheelDirection(element, callback, { preventDefault = true, threshold = 0 } = {}) { 9060 const handler = (e) => { 9061 if (preventDefault) e.preventDefault(); 9062 9063 const delta = e.deltaY; 9064 if (Math.abs(delta) <= threshold) return; 9065 9066 callback(delta < 0 ? 'up' : 'down', e); 9067 }; 9068 9069 element.addEventListener('wheel', handler, { passive: !preventDefault }); 9070 9071 return () => element.removeEventListener('wheel', handler); 9072} 9073 9074 9075 9076function onRightClickDrag(element, { 9077 onStart, 9078 onDrag, 9079 onEnd, 9080 throttleMs = 0, 9081} = {}) { 9082 let dragging = false; 9083 let didDrag = false; 9084 let startX, startY, lastX, lastY; 9085 let lastCall = 0; 9086 let pending = null; 9087 9088 window.addEventListener('contextmenu', e => { 9089 if (didDrag) { 9090 e.preventDefault(); 9091 didDrag = false; 9092 } 9093 }, { capture: true }); 9094 9095 element.addEventListener('mousedown', e => { 9096 if (e.button !== 2) return; 9097 e.preventDefault(); 9098 dragging = true; 9099 didDrag = false; 9100 startX = lastX = e.clientX; 9101 startY = lastY = e.clientY; 9102 lastCall = 0; 9103 onStart?.({ x: startX, y: startY }); 9104 }); 9105 9106 window.addEventListener('mousemove', e => { 9107 if (!dragging) return; 9108 didDrag = true; 9109 const dx = e.clientX - lastX; 9110 const dy = e.clientY - lastY; 9111 lastX = e.clientX; 9112 lastY = e.clientY; 9113 9114 const payload = { 9115 dx, dy, 9116 totalDx: e.clientX - startX, 9117 totalDy: e.clientY - startY, 9118 x: e.clientX, 9119 y: e.clientY, 9120 }; 9121 9122 if (!throttleMs) { 9123 onDrag?.(payload); 9124 return; 9125 } 9126 9127 const now = Date.now(); 9128 clearTimeout(pending); 9129 9130 if (now - lastCall >= throttleMs) { 9131 lastCall = now; 9132 onDrag?.(payload); 9133 } else { 9134 pending = setTimeout(() => { 9135 lastCall = Date.now(); 9136 onDrag?.(payload); 9137 }, throttleMs - (now - lastCall)); 9138 } 9139 }, { capture: true }); 9140 9141 window.addEventListener('mouseup', e => { 9142 if (!dragging || e.button !== 2) return; 9143 dragging = false;
9144 clearTimeout(pending); 9145 onEnd?.({ 9146 totalDx: e.clientX - startX, 9147 totalDy: e.clientY - startY, 9148 }); 9149 }, { capture: true }); 9150} 9151 9152function fadeIn(el, duration = 200) { 9153 el.style.opacity = 0; 9154 el.style.display = 'block'; 9155 const start = performance.now(); 9156 (function step(now) { 9157 const p = Math.min((now - start) / duration, 1); 9158 el.style.opacity = p; 9159 if (p < 1) requestAnimationFrame(step); 9160 })(start); 9161} 9162 9163// napisz funkcjÄ zwracajÄ cÄ informacje z wikidata o danym obiekcie 9164// https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q42&format=json&props=claims 9165// https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q42&format=json&props=claims|labels|descriptions|sitelinks/urls 9166// https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q42&format=json&props=claims|labels|descriptions|sitelinks/urls&languages=pl 9167// https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q42&format=json&props=claims|labels|descriptions|sitelinks/urls&languages=pl&languagefallback=1 9168
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.