From 9e21173d284084f96f53b054ba05e6ab4fe31376 Mon Sep 17 00:00:00 2001 From: Grandro Date: Tue, 1 Aug 2023 12:16:17 +0200 Subject: [PATCH 1/9] Added loading bar visually, properly centered message, minor renames --- web/index.html | 11 +- web/style.css | 785 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 712 insertions(+), 84 deletions(-) diff --git a/web/index.html b/web/index.html index b586cb0..bc6de25 100755 --- a/web/index.html +++ b/web/index.html @@ -22,12 +22,19 @@
-
+
Loading results from QLever...
-
+ +
+
+
75%
+
+
+
+ diff --git a/web/style.css b/web/style.css index 491c547..778d57e 100755 --- a/web/style.css +++ b/web/style.css @@ -1,112 +1,690 @@ -.leaflet-image-layer,.leaflet-layer,.leaflet-pane,.leaflet-pane>canvas,.leaflet-pane>svg,.leaflet-tile,.leaflet-tile-container,.leaflet-zoom-box{position:absolute;left:0;top:0}.leaflet-container{overflow:hidden}.leaflet-tile{-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::selection{background:0 0}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{width:1600px;height:1600px;-webkit-transform-origin:0 0}.leaflet-container .leaflet-overlay-pane svg,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer{max-width:none!important;max-height:none!important}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:rgba(51,181,229,.4)}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{width:0;height:0;-moz-box-sizing:border-box;box-sizing:border-box;z-index:800}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{position:relative;z-index:800;pointer-events:visiblePainted;pointer-events:auto}.leaflet-bottom,.leaflet-top{position:absolute;z-index:1000;pointer-events:none}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-tile{will-change:opacity}.leaflet-fade-anim .leaflet-popup{opacity:0;-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}.leaflet-zoom-anim .leaflet-zoom-animated{will-change:transform}.leaflet-zoom-anim .leaflet-zoom-animated{-webkit-transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);-moz-transition:-moz-transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1)}.leaflet-pan-anim .leaflet-tile,.leaflet-zoom-anim .leaflet-tile{-webkit-transition:none;-moz-transition:none;transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-control,.leaflet-popup-pane{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive{cursor:move;cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.leaflet-image-layer,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-image-layer.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{background:#ddd;outline:0}.leaflet-container a{color:#0078a8}.leaflet-container a.leaflet-active{outline:2px solid orange}.leaflet-zoom-box{border:2px dotted #38f;background:rgba(255,255,255,.5)}.leaflet-bar{box-shadow:0 1px 5px rgba(0,0,0,.65);border-radius:4px}.leaflet-bar a,.leaflet-bar a:hover{background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;display:block;text-align:center;text-decoration:none;color:#000}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50% 50%;background-repeat:no-repeat;display:block}.leaflet-bar a:hover{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-bottom:none}.leaflet-bar a.leaflet-disabled{cursor:default;background-color:#f4f4f4;color:#bbb}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-left-radius:2px;border-bottom-right-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{text-indent:1px}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-container .leaflet-control-attribution{background:#fff;background:rgba(255,255,255,.7);margin:0}.leaflet-control-attribution{padding:0 5px;color:#333}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:hover{text-decoration:underline}.leaflet-container .leaflet-control-attribution{font-size:11px}.leaflet-touch .leaflet-bar,.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers{box-shadow:none}.leaflet-touch .leaflet-bar,.leaflet-touch .leaflet-control-layers{border:2px solid rgba(0,0,0,.2);background-clip:padding-box}.leaflet-popup{position:absolute;text-align:center;margin-bottom:20px}.leaflet-popup-content-wrapper{padding:1px;text-align:left;border-radius:12px}.leaflet-popup-content{margin:13px 19px;line-height:1.4}.leaflet-popup-content p{margin:18px 0}.leaflet-popup-tip-container{width:40px;height:20px;position:absolute;left:50%;margin-left:-20px;overflow:hidden;pointer-events:none}.leaflet-popup-tip{width:17px;height:17px;padding:1px;margin:-10px auto 0;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{background:#fff;color:#333;box-shadow:0 3px 14px rgba(0,0,0,.4)}.leaflet-container a.leaflet-popup-close-button{position:absolute;top:0;right:0;padding:5px 5px 0 0;border:none;text-align:center;width:22px;height:18px;font:24px/22px Tahoma,Verdana,sans-serif;color:#c3c3c3;text-decoration:none;font-weight:700;background:0 0}.leaflet-container a.leaflet-popup-close-button:hover{color:#999}.leaflet-popup-scrolled{overflow:auto;border-bottom:1px solid #ddd;border-top:1px solid #ddd}body{padding:0;margin:0;font-family:sans-serif}body,html{height:100%;width:100%}tt{letter-spacing:-.5px}#attr-tbl tr{background-color:#c0f7c0}.stat-label{white-space:nowrap;background:0 0}#attr-tbl .err-10{background-color:red}#attr-tbl .err-9{background-color:#ff1010}#attr-tbl .err-8{background-color:#f92424}#attr-tbl .err-7{background-color:#f93838}#attr-tbl .err-6{background-color:#f56262}#attr-tbl .err-5{background-color:#f38484}.attr-err-info{margin-top:5px;font-size:13px;font-style:italic}.leaflet-popup-content-wrapper{border-radius:5px}#attr-tbl .err-wrap{max-height:100px;display:block;overflow-y:auto}#attr-tbl{margin-top:10px;width:100%}.nmt,.omt,.sugtit{font-style:normal;font-weight:700}#gsn,#gso{margin-top:8px;padding-top:4px;border-top:solid 1px #aaa}#gsn div{background-color:#b6b6e4;border-left:solid 4px #00f;padding:2px;margin:2px;padding-left:5px}#gso div{border-left:solid 4px #78f378;padding:2px;margin:2px;padding-left:5px}#sugg{margin-top:8px;padding-top:4px;border-top:solid 1px #aaa;color:#0000c3}#sugg ul{padding:5px;margin:5px;padding-top:0} #m,main{width:100%;height:100%}#msg {position:absolute;width:400px;border-radius:4px;top:50%;left:50%;margin-left:-200px;margin-top:-150px;background-color:white;border:solid 1px black;text-align:center;z-index:99999;padding:50px;}#m{cursor: pointer;} +.leaflet-image-layer, .leaflet-layer, .leaflet-pane, .leaflet-pane>canvas, .leaflet-pane>svg, .leaflet-tile, .leaflet-tile-container, .leaflet-zoom-box { + position: absolute; + left: 0; + top: 0 +} + +.leaflet-container { + overflow: hidden +} + +.leaflet-tile { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none +} + +.leaflet-tile::selection { + background: 0 0 +} + +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast +} + +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0 +} + +.leaflet-container .leaflet-overlay-pane svg, .leaflet-container .leaflet-shadow-pane img, .leaflet-container .leaflet-tile, .leaflet-container .leaflet-tile-pane img, .leaflet-container img.leaflet-image-layer { + max-width: none !important; + max-height: none !important +} + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y +} + +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + touch-action: none; + touch-action: pinch-zoom +} + +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none +} + +.leaflet-container { + -webkit-tap-highlight-color: transparent +} + +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, .4) +} + +.leaflet-tile { + filter: inherit; + visibility: hidden +} + +.leaflet-tile-loaded { + visibility: inherit +} + +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800 +} + +.leaflet-overlay-pane svg { + -moz-user-select: none +} + +.leaflet-pane { + z-index: 400 +} + +.leaflet-tile-pane { + z-index: 200 +} + +.leaflet-overlay-pane { + z-index: 400 +} + +.leaflet-shadow-pane { + z-index: 500 +} + +.leaflet-marker-pane { + z-index: 600 +} + +.leaflet-tooltip-pane { + z-index: 650 +} + +.leaflet-popup-pane { + z-index: 700 +} + +.leaflet-map-pane canvas { + z-index: 100 +} + +.leaflet-map-pane svg { + z-index: 200 +} + +.leaflet-vml-shape { + width: 1px; + height: 1px +} + +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute +} + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; + pointer-events: auto +} + +.leaflet-bottom, .leaflet-top { + position: absolute; + z-index: 1000; + pointer-events: none +} + +.leaflet-top { + top: 0 +} + +.leaflet-right { + right: 0 +} + +.leaflet-bottom { + bottom: 0 +} + +.leaflet-left { + left: 0 +} + +.leaflet-control { + float: left; + clear: both +} + +.leaflet-right .leaflet-control { + float: right +} + +.leaflet-top .leaflet-control { + margin-top: 10px +} + +.leaflet-bottom .leaflet-control { + margin-bottom: 10px +} + +.leaflet-left .leaflet-control { + margin-left: 10px +} + +.leaflet-right .leaflet-control { + margin-right: 10px +} + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity +} + +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity .2s linear; + -moz-transition: opacity .2s linear; + transition: opacity .2s linear +} + +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1 +} + +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0 +} + +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform +} + +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform .25s cubic-bezier(0, 0, .25, 1); + -moz-transition: -moz-transform .25s cubic-bezier(0, 0, .25, 1); + transition: transform .25s cubic-bezier(0, 0, .25, 1) +} + +.leaflet-pan-anim .leaflet-tile, .leaflet-zoom-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none +} + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden +} + +.leaflet-interactive { + cursor: pointer +} + +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab +} + +.leaflet-control, .leaflet-popup-pane { + cursor: auto +} + +.leaflet-dragging .leaflet-grab, .leaflet-dragging .leaflet-grab .leaflet-interactive { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing +} + +.leaflet-image-layer, .leaflet-pane>svg path, .leaflet-tile-container { + pointer-events: none +} + +.leaflet-image-layer.leaflet-interactive, .leaflet-pane>svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; + pointer-events: auto +} + +.leaflet-container { + background: #ddd; + outline: 0 +} + +.leaflet-container a { + color: #0078a8 +} + +.leaflet-container a.leaflet-active { + outline: 2px solid orange +} + +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255, 255, 255, .5) +} + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0, 0, 0, .65); + border-radius: 4px +} + +.leaflet-bar a, .leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: #000 +} + +.leaflet-bar a, .leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block +} + +.leaflet-bar a:hover { + background-color: #f4f4f4 +} + +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px +} + +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none +} + +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb +} + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px +} + +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px +} + +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px +} + +.leaflet-control-zoom-in, .leaflet-control-zoom-out { + text-indent: 1px +} + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px +} + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, .7); + margin: 0 +} + +.leaflet-control-attribution { + padding: 0 5px; + color: #333 +} + +.leaflet-control-attribution a { + text-decoration: none +} + +.leaflet-control-attribution a:hover { + text-decoration: underline +} + +.leaflet-container .leaflet-control-attribution { + font-size: 11px +} + +.leaflet-touch .leaflet-bar, .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers { + box-shadow: none +} + +.leaflet-touch .leaflet-bar, .leaflet-touch .leaflet-control-layers { + border: 2px solid rgba(0, 0, 0, .2); + background-clip: padding-box +} + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px +} + +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px +} + +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4 +} + +.leaflet-popup-content p { + margin: 18px 0 +} + +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none +} + +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + margin: -10px auto 0; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg) +} + +.leaflet-popup-content-wrapper, .leaflet-popup-tip { + background: #fff; + color: #333; + box-shadow: 0 3px 14px rgba(0, 0, 0, .4) +} + +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 5px 5px 0 0; + border: none; + text-align: center; + width: 22px; + height: 18px; + font: 24px/22px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: 700; + background: 0 0 +} + +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999 +} + +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd +} + +body { + padding: 0; + margin: 0; + font-family: sans-serif +} + +body, html { + height: 100%; + width: 100% +} + +tt { + letter-spacing: -.5px +} + +#attr-tbl tr { + background-color: #c0f7c0 +} + +.stat-label { + white-space: nowrap; + background: 0 0 +} + +#attr-tbl .err-10 { + background-color: red +} + +#attr-tbl .err-9 { + background-color: #ff1010 +} + +#attr-tbl .err-8 { + background-color: #f92424 +} + +#attr-tbl .err-7 { + background-color: #f93838 +} + +#attr-tbl .err-6 { + background-color: #f56262 +} + +#attr-tbl .err-5 { + background-color: #f38484 +} + +.attr-err-info { + margin-top: 5px; + font-size: 13px; + font-style: italic +} + +.leaflet-popup-content-wrapper { + border-radius: 5px +} + +#attr-tbl .err-wrap { + max-height: 100px; + display: block; + overflow-y: auto +} + +#attr-tbl { + margin-top: 10px; + width: 100% +} + +.nmt, .omt, .sugtit { + font-style: normal; + font-weight: 700 +} + +#gsn, #gso { + margin-top: 8px; + padding-top: 4px; + border-top: solid 1px #aaa +} + +#gsn div { + background-color: #b6b6e4; + border-left: solid 4px #00f; + padding: 2px; + margin: 2px; + padding-left: 5px +} + +#gso div { + border-left: solid 4px #78f378; + padding: 2px; + margin: 2px; + padding-left: 5px +} + +#sugg { + margin-top: 8px; + padding-top: 4px; + border-top: solid 1px #aaa; + color: #0000c3 +} + +#sugg ul { + padding: 5px; + margin: 5px; + padding-top: 0 +} + +#m, main { + width: 100%; + height: 100% +} + +#m { + cursor: pointer; +} .leaflet-control-layers { - box-shadow: 0 1px 5px rgba(0,0,0,0.4); - background: #fff; - border-radius: 5px; - } + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); + background: #fff; + border-radius: 5px; +} + .leaflet-control-layers-toggle { - text-decoration: none; - text-align: center; - width: 36px; - height: 36px; - color: black; - } + text-decoration: none; + text-align: center; + width: 36px; + height: 36px; + color: black; +} + .leaflet-control-layers-toggle:after { - content: "🗺"; - line-height:45px; - font-size:25px; - color: black; - } + content: "🗺"; + line-height: 45px; + font-size: 25px; + color: black; +} + .leaflet-retina .leaflet-control-layers-toggle { - background-image: url(images/layers-2x.png); - background-size: 26px 26px; - } + background-image: url(images/layers-2x.png); + background-size: 26px 26px; +} + .leaflet-touch .leaflet-control-layers-toggle { - width: 44px; - height: 44px; - } -.leaflet-control-layers .leaflet-control-layers-list, -.leaflet-control-layers-expanded .leaflet-control-layers-toggle { - display: none; - } + width: 44px; + height: 44px; +} + +.leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; +} + .leaflet-control-layers-expanded .leaflet-control-layers-list { - display: block; - position: relative; - } + display: block; + position: relative; +} + .leaflet-control-layers-expanded { - padding: 6px 10px 6px 6px; - color: #333; - background: #fff; - } + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; +} + .leaflet-control-layers-scrollbar { - overflow-y: scroll; - overflow-x: hidden; - padding-right: 5px; - } + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; +} + .leaflet-control-layers-selector { - margin-top: 2px; - position: relative; - top: 1px; - } + margin-top: 2px; + position: relative; + top: 1px; +} + .leaflet-control-layers label { - display: block; - font-size: 13px; - font-size: 1.08333em; - } + display: block; + font-size: 13px; + font-size: 1.08333em; +} + .leaflet-control-layers-separator { - height: 0; - border-top: 1px solid #ddd; - margin: 5px -10px 5px -6px; - } + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; +} h1, #msg-inner { - font: 1.5em sans-serif; - margin-bottom: 40px; - opacity: 0.8; + font: 1.5em sans-serif; + margin-bottom: 40px; + opacity: 0.8; +} + +#msg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 25%; + border-radius: 4px; + background-color: white; + border: solid 1px black; + text-align: center; + z-index: 99999; + padding: 50px; } #msg-inner-desc { - font: 0.9em mono; - text-align: left; - background-color: #fefefe; - width: 100%; - max-height: 200px; - overflow-y: auto; - overflow-x: auto; -} - -#loader { - display: inline-block; - width: 35px; - height: 35px; - border: 3px solid rgba(0,0,0,.3); - border-radius: 50%; - border-top-color: #fff; - animation: spin 1s ease-in-out infinite; - -webkit-animation: spin 1s ease-in-out infinite; + font: 0.9em mono; + text-align: left; + background-color: #fefefe; + width: 100%; + max-height: 200px; + overflow-y: auto; + overflow-x: auto; } @keyframes spin { - to { -webkit-transform: rotate(360deg); } + to { + -webkit-transform: rotate(360deg); + } } + @-webkit-keyframes spin { - to { -webkit-transform: rotate(360deg); } + to { + -webkit-transform: rotate(360deg); + } } .leaflet-popup-content img { - width:200px; + width: 200px; } .leaflet-popup-content a { - border: none; + border: none; } #ex { - position: absolute; - top: 10px; - right: 10px; - z-index:9999; + position: absolute; + top: 10px; + right: 10px; + z-index: 9999; } #stats { @@ -120,12 +698,55 @@ h1, #msg-inner { background: white; } +#load { + display: grid; + + width: 100%; + background-color: #ddd; + + border-radius: 4px; + border: solid 1px black; +} + +#load-bar { + grid-column: 1; + grid-row: 1; + + width: 75%; + height: 30px; + background-color: #04AA6D; +} + +#load-text { + grid-column: 1; + grid-row: 1; + + text-align: center; + font-size: 15px; + line-height: 30px; + color: white; +} + +#load-spinner { + position: absolute; + right: 8px; + top: 8px; + display: inline-block; + width: 16px; + height: 16px; + border: 3px solid rgba(0, 0, 0, .3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; + -webkit-animation: spin 1s ease-in-out infinite; +} + #stats span { padding: 5px; } #ex button { - padding: 6px 12px; + padding: 6px 12px; margin-bottom: 0; font-size: 15px; -ms-touch-action: manipulation; @@ -143,7 +764,7 @@ h1, #msg-inner { line-height: 1.42857143; text-align: center; white-space: nowrap; - background-color:white; + background-color: white; } #ex button:hover { @@ -151,5 +772,5 @@ h1, #msg-inner { } .export-link { - font-size: 80%; -} + font-size: 80%; +} \ No newline at end of file From 3da56e6eb6475590309d2e1c8aff9e3abe08a255 Mon Sep 17 00:00:00 2001 From: Grandro Date: Thu, 10 Aug 2023 19:03:27 +0200 Subject: [PATCH 2/9] Implemented a loading bar by fetching the GeomCache progress from frontend to backend in a fixed time interval. Edited CMakeLists.txt to only capitalize the first character of CMAKE_BUILD_TYPE. Ensured semicolon consistency in script.js. --- CMakeLists.txt | 16 ++++- src/qlever-petrimaps/GeomCache.cpp | 11 +++- src/qlever-petrimaps/GeomCache.h | 2 + src/qlever-petrimaps/server/Server.cpp | 26 +++++++- src/qlever-petrimaps/server/Server.h | 2 + web/index.html | 2 +- web/script.js | 85 +++++++++++++++++++------- web/style.css | 2 +- 8 files changed, 115 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c03e07b..5f0a47b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,10 +2,24 @@ cmake_minimum_required (VERSION 2.8) project (qlever-mapui) +include(CMakePrintHelpers) + +cmake_print_variables(CMAKE_BUILD_TYPE) + +# only change first char if (CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE) + string(SUBSTRING ${CMAKE_BUILD_TYPE} 0 1 FIRST_CHAR) + string(TOUPPER ${FIRST_CHAR} FIRST_CHAR) + string(REGEX REPLACE "^.(.*)" "${FIRST_CHAR}\\1" CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}") endif() +# old solution +#if (CMAKE_BUILD_TYPE) +# string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE) +#endif() + +cmake_print_variables(CMAKE_BUILD_TYPE) + enable_testing() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") diff --git a/src/qlever-petrimaps/GeomCache.cpp b/src/qlever-petrimaps/GeomCache.cpp index 8c31472..a1f470d 100644 --- a/src/qlever-petrimaps/GeomCache.cpp +++ b/src/qlever-petrimaps/GeomCache.cpp @@ -291,8 +291,7 @@ void GeomCache::parse(const char* c, size_t size) { LOG(INFO) << "[GEOMCACHE] " << "@ row " << _curRow << " (" << std::fixed << std::setprecision(2) - << (static_cast(_curRow) / - static_cast(_totalSize) * 100) + << getLoadStatusPercent() << "%, " << _pointsFSize << " points, " << _linesFSize << " (open) polygons)"; } @@ -318,6 +317,14 @@ void GeomCache::parse(const char* c, size_t size) { } } +double GeomCache::getLoadStatusPercent() { + if (_totalSize == 0) { + return 0.0; + } else { + return (static_cast(_curRow) / static_cast(_totalSize) * 100); + } +} + // _____________________________________________________________________________ void GeomCache::parseIds(const char* c, size_t size) { size_t lastQid = -1; diff --git a/src/qlever-petrimaps/GeomCache.h b/src/qlever-petrimaps/GeomCache.h index f47e221..cf46451 100644 --- a/src/qlever-petrimaps/GeomCache.h +++ b/src/qlever-petrimaps/GeomCache.h @@ -89,6 +89,8 @@ class GeomCache { return id + 1 < _lines.size() ? _lines[id + 1] : _linePoints.size(); } + double getLoadStatusPercent(); + private: std::string _backendUrl; CURL* _curl; diff --git a/src/qlever-petrimaps/server/Server.cpp b/src/qlever-petrimaps/server/Server.cpp index 5c7174a..f982fb1 100755 --- a/src/qlever-petrimaps/server/Server.cpp +++ b/src/qlever-petrimaps/server/Server.cpp @@ -81,6 +81,8 @@ util::http::Answer Server::handle(const util::http::Req& req, int con) const { a = handlePosReq(params); } else if (cmd == "/export") { a = handleExportReq(params, con); + } else if (cmd == "/loadstatus") { + a = handleLoadStatusReq(params); } else if (cmd == "/build.js") { a = util::http::Answer( "200 OK", std::string(build_js, build_js + sizeof build_js / @@ -745,6 +747,7 @@ util::http::Answer Server::handleLoadReq(const Params& pars) const { LOG(INFO) << "[SERVER] Queried backend is " << backend; + createCache(backend); loadCache(backend); auto answ = util::http::Answer("200 OK", "{}"); @@ -764,6 +767,7 @@ util::http::Answer Server::handleQueryReq(const Params& pars) const { LOG(INFO) << "[SERVER] Queried backend is " << backend; LOG(INFO) << "[SERVER] Query is:\n" << query; + createCache(backend); loadCache(backend); std::string queryId = backend + "$" + query; @@ -1108,6 +1112,18 @@ util::http::Answer Server::handleExportReq(const Params& pars, int sock) const { return aw; } +util::http::Answer Server::handleLoadStatusReq(const Params& pars) const { + if (pars.count("backend") == 0 || pars.find("backend")->second.empty()) + throw std::invalid_argument("No backend (?backend=) specified."); + auto backend = pars.find("backend")->second; + createCache(backend); + std::shared_ptr cache = _caches[backend]; + double loadStatusPercent = cache->getLoadStatusPercent(); + util::http::Answer ans = util::http::Answer("200 OK", std::to_string(loadStatusPercent)); + + return ans; +} + // _____________________________________________________________________________ void Server::drawPoint(std::vector& points, std::vector& points2, int px, int py, int w, @@ -1140,9 +1156,7 @@ std::string Server::getSessionId() const { return std::to_string(d(rng)); } -// _____________________________________________________________________________ -void Server::loadCache(const std::string& backend) const { - std::shared_ptr reqor; +void Server::createCache(const std::string& backend) const { std::shared_ptr cache; { @@ -1154,6 +1168,12 @@ void Server::loadCache(const std::string& backend) const { _caches[backend] = cache; } } +} + +// _____________________________________________________________________________ +void Server::loadCache(const std::string& backend) const { + //std::shared_ptr reqor; + std::shared_ptr cache = _caches[backend]; try { cache->load(_cacheDir); diff --git a/src/qlever-petrimaps/server/Server.h b/src/qlever-petrimaps/server/Server.h index a2c9444..14e0a5f 100755 --- a/src/qlever-petrimaps/server/Server.h +++ b/src/qlever-petrimaps/server/Server.h @@ -40,7 +40,9 @@ class Server : public util::http::Handler { util::http::Answer handleLoadReq(const Params& pars) const; util::http::Answer handleExportReq(const Params& pars, int sock) const; + util::http::Answer handleLoadStatusReq(const Params& pars) const; + void createCache(const std::string& backend) const; void loadCache(const std::string& backend) const; void clearSession(const std::string& id) const; diff --git a/web/index.html b/web/index.html index bc6de25..6d3bc16 100755 --- a/web/index.html +++ b/web/index.html @@ -30,7 +30,7 @@
-
75%
+
0.00%
diff --git a/web/script.js b/web/script.js index b605449..64f99a5 100755 --- a/web/script.js +++ b/web/script.js @@ -1,26 +1,29 @@ -var sessionId; -var curGeojson; -var curGeojsonId = -1; +let sessionId; +let curGeojson; +let curGeojsonId = -1; -var urlParams = new URLSearchParams(window.location.search); -var qleverBackend = urlParams.get("backend"); -var query = urlParams.get("query"); +let urlParams = new URLSearchParams(window.location.search); +let qleverBackend = urlParams.get("backend"); +let query = urlParams.get("query"); -var map = L.map('m', { +// id of SetInterval to stop loadStatus requests on error or load finish +let loadStatusIntervalId = -1; + +let map = L.map('m', { renderer: L.canvas(), preferCanvas: true }).setView([47.9965, 7.8469], 13); map.attributionControl.setPrefix('University of Freiburg'); -var layerControl = L.control.layers([], [], {collapsed:true, position: 'topleft'}).addTo(map); +let layerControl = L.control.layers([], [], {collapsed:true, position: 'topleft'}).addTo(map); -var osmLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { +let osmLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap', maxZoom: 19, opacity:0.9 }).addTo(map); -var genError = "

Session has been removed from cache.

Resend request

"; +let genError = "

Session has been removed from cache.

Resend request

"; function openPopup(data) { if (data.length > 0) { @@ -71,7 +74,7 @@ function openPopup(data) { // the result value as another. Reformat a bit, so that it looks nice in // an HTML table. let key = variable.substring(1); - if (row[i] == null) { row[i] = "---"; } + if (row[i] == null) { row[i] = "---" } let value = row[i].replace(/\\([()])/g, "$1") .replace(/<((.*)\/(.*))>/, "$3") @@ -116,14 +119,13 @@ function getGeoJsonLayer(geom) { opacity: 1, fillOpacity: 0.2 });} - }) + }); } function showError(error) { error = error.toString(); - console.log(error); document.getElementById("msg").style.display = "block"; - document.getElementById("loader").style.display = "none"; + document.getElementById("load").style.display = "none"; document.getElementById("msg-inner").style.color = "red"; document.getElementById("msg-inner").style.fontSize = "20px"; document.getElementById("msg-inner").innerHTML = error.split("\n")[0]; @@ -188,7 +190,7 @@ function loadMap(id, bounds, numObjects) { layerControl.addBaseLayer(autoLayer, "Auto"); map.on('click', function(e) { - const ll= e.latlng; + const ll = e.latlng; const pos = L.Projection.SphericalMercator.project(ll); const w = map.getPixelBounds().max.x - map.getPixelBounds().min.x; @@ -224,15 +226,52 @@ function loadMap(id, bounds, numObjects) { }); } -console.log("Fetching results...") -fetch('query' + window.location.search) - .then(response => { - if (!response.ok) return response.text().then(text => {throw new Error(text)}); - return response; +function updateLoad(percent) { + const barElem = document.getElementById("load-bar"); + const textElem = document.getElementById("load-text"); + barElem.style.width = percent + "%"; + textElem.innerHTML = percent.toString() + "%"; +} + +function fetchResults() { + console.log("Fetching results..."); + fetch('query' + window.location.search) + .then(response => { + if (!response.ok) return response.text().then(text => {throw new Error(text)}); + return response; + }) + .then(response => response.json()) + .then(data => { + clearInterval(loadStatusIntervalId); + loadMap(data["qid"], data["bounds"], data["numobjects"]); }) - .then(response => response.json()) - .then(data => loadMap(data["qid"], data["bounds"], data["numobjects"])) - .catch(error => {showError(error);}); + .catch(error => showError(error)); +} + +function fetchLoadStatusInterval(interval) { + fetchLoadStatus(interval); + loadStatusIntervalId = setInterval(fetchLoadStatus, interval); +} + +async function fetchLoadStatus(interval) { + console.log("Fetching load status..."); + + fetch('loadstatus?backend=' + qleverBackend) + .then(response => { + if (!response.ok) return response.text().then(text => {throw new Error(text)}); + return response; + }) + .then(response => response.text()) + .then(percentText => parseFloat(percentText).toFixed(2)) + .then(percent => updateLoad(percent)) + .catch(error => { + showError(error); + clearInterval(loadStatusIntervalId); + }); +} + +fetchResults(); +fetchLoadStatusInterval(1000); document.getElementById("ex-geojson").onclick = function() { if (!sessionId) return; diff --git a/web/style.css b/web/style.css index 778d57e..c19d3d8 100755 --- a/web/style.css +++ b/web/style.css @@ -712,7 +712,7 @@ h1, #msg-inner { grid-column: 1; grid-row: 1; - width: 75%; + width: 0%; height: 30px; background-color: #04AA6D; } From bc320e5531bfec97b1a871d31e3467adb6ac485b Mon Sep 17 00:00:00 2001 From: Grandro Date: Wed, 13 Sep 2023 16:15:19 +0200 Subject: [PATCH 3/9] Improved loading bar visuals, merged progress of all loading stages into one total progress, displays current stage to the user. --- src/qlever-petrimaps/GeomCache.cpp | 31 ++++++++++++++++++++++++-- src/qlever-petrimaps/GeomCache.h | 4 ++++ src/qlever-petrimaps/server/Server.cpp | 6 ++++- web/index.html | 30 +++++++++++++------------ web/script.js | 29 +++++++++++++++++------- web/style.css | 10 +++++---- 6 files changed, 81 insertions(+), 29 deletions(-) diff --git a/src/qlever-petrimaps/GeomCache.cpp b/src/qlever-petrimaps/GeomCache.cpp index a1f470d..c0a14cf 100644 --- a/src/qlever-petrimaps/GeomCache.cpp +++ b/src/qlever-petrimaps/GeomCache.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "qlever-petrimaps/GeomCache.h" #include "qlever-petrimaps/Misc.h" @@ -95,6 +96,8 @@ size_t GeomCache::writeCbCount(void* contents, size_t size, size_t nmemb, // _____________________________________________________________________________ void GeomCache::parse(const char* c, size_t size) { + _loadStatusStage = _LoadStatusStages::Parse; + const char* start = c; while (c < start + size) { if (_raw.size() < 10000) _raw.push_back(*c); @@ -318,15 +321,39 @@ void GeomCache::parse(const char* c, size_t size) { } double GeomCache::getLoadStatusPercent() { + /* + There are 2 loading stages: Parse, afterwards ParseIds. + Because ParseIds is usually pretty short, we merge the progress of both stages to one total progress. + Progress is calculated by _curRow / _totalSize, which are handled by each stage individually. + */ if (_totalSize == 0) { return 0.0; - } else { - return (static_cast(_curRow) / static_cast(_totalSize) * 100); } + + float parsePercent = 95.0f; + float parseIdsPercent = 5.0f; + float totalPercent = 0.0f; + switch(_loadStatusStage) { + case _LoadStatusStages::Parse: + totalPercent = std::atomic(_curRow) / static_cast(_totalSize) * parsePercent; + break; + case _LoadStatusStages::ParseIds: + totalPercent = parsePercent; + totalPercent += std::atomic(_curRow) / static_cast(_totalSize) * parseIdsPercent; + break; + } + + return totalPercent; +} + +int GeomCache::getLoadStatusStage() { + return _loadStatusStage; } // _____________________________________________________________________________ void GeomCache::parseIds(const char* c, size_t size) { + _loadStatusStage = _LoadStatusStages::ParseIds; + size_t lastQid = -1; for (size_t i = 0; i < size; i++) { if (_raw.size() < 10000) _raw.push_back(c[i]); diff --git a/src/qlever-petrimaps/GeomCache.h b/src/qlever-petrimaps/GeomCache.h index cf46451..5ac4e67 100644 --- a/src/qlever-petrimaps/GeomCache.h +++ b/src/qlever-petrimaps/GeomCache.h @@ -90,6 +90,7 @@ class GeomCache { } double getLoadStatusPercent(); + int getLoadStatusStage(); private: std::string _backendUrl; @@ -99,6 +100,9 @@ class GeomCache { ID _curId; QLEVER_ID_TYPE _maxQid; size_t _curRow, _curUniqueGeom; + + enum _LoadStatusStages {Parse = 1, ParseIds}; + _LoadStatusStages _loadStatusStage; static size_t writeCb(void* contents, size_t size, size_t nmemb, void* userp); static size_t writeCbIds(void* contents, size_t size, size_t nmemb, diff --git a/src/qlever-petrimaps/server/Server.cpp b/src/qlever-petrimaps/server/Server.cpp index f982fb1..e56948e 100755 --- a/src/qlever-petrimaps/server/Server.cpp +++ b/src/qlever-petrimaps/server/Server.cpp @@ -1119,7 +1119,11 @@ util::http::Answer Server::handleLoadStatusReq(const Params& pars) const { createCache(backend); std::shared_ptr cache = _caches[backend]; double loadStatusPercent = cache->getLoadStatusPercent(); - util::http::Answer ans = util::http::Answer("200 OK", std::to_string(loadStatusPercent)); + int loadStatusStage = cache->getLoadStatusStage(); + + std::stringstream json; + json << "{\"percent\": " << loadStatusPercent << ", \"stage\": " << loadStatusStage << "}"; + util::http::Answer ans = util::http::Answer("200 OK", json.str()); return ans; } diff --git a/web/index.html b/web/index.html index 6d3bc16..15a09a7 100755 --- a/web/index.html +++ b/web/index.html @@ -16,26 +16,28 @@
-
- - - -
-
-
+
+ + + +
+
-
-
Loading results from QLever...
-
+
+
Loading results from QLever
+
-
-
0.00%
-
+
Parsing geometry... (1/2)
+
+
+
0.00%
+
+
-
+ diff --git a/web/script.js b/web/script.js index 64f99a5..101cf24 100755 --- a/web/script.js +++ b/web/script.js @@ -226,15 +226,25 @@ function loadMap(id, bounds, numObjects) { }); } -function updateLoad(percent) { +function updateLoad(stage, percent) { + const stageElem = document.getElementById("load-stage"); const barElem = document.getElementById("load-bar"); - const textElem = document.getElementById("load-text"); + const percentElem = document.getElementById("load-percent"); + switch (stage) { + case 1: + stageElem.innerHTML = "Parsing geometry... (1/2)"; + break; + case 2: + stageElem.innerHTML = "Parsing geometry Ids... (2/2)"; + break; + } barElem.style.width = percent + "%"; - textElem.innerHTML = percent.toString() + "%"; + percentElem.innerHTML = percent.toString() + "%"; } function fetchResults() { console.log("Fetching results..."); + fetch('query' + window.location.search) .then(response => { if (!response.ok) return response.text().then(text => {throw new Error(text)}); @@ -249,11 +259,11 @@ function fetchResults() { } function fetchLoadStatusInterval(interval) { - fetchLoadStatus(interval); + fetchLoadStatus(); loadStatusIntervalId = setInterval(fetchLoadStatus, interval); } -async function fetchLoadStatus(interval) { +async function fetchLoadStatus() { console.log("Fetching load status..."); fetch('loadstatus?backend=' + qleverBackend) @@ -261,9 +271,12 @@ async function fetchLoadStatus(interval) { if (!response.ok) return response.text().then(text => {throw new Error(text)}); return response; }) - .then(response => response.text()) - .then(percentText => parseFloat(percentText).toFixed(2)) - .then(percent => updateLoad(percent)) + .then(response => response.json()) + .then(data => { + var stage = data["stage"]; + var percent = parseFloat(data["percent"]).toFixed(2); + updateLoad(stage, percent); + }) .catch(error => { showError(error); clearInterval(loadStatusIntervalId); diff --git a/web/style.css b/web/style.css index c19d3d8..ce7ef73 100755 --- a/web/style.css +++ b/web/style.css @@ -698,11 +698,11 @@ h1, #msg-inner { background: white; } -#load { +#load-status { display: grid; width: 100%; - background-color: #ddd; + background-color: white; border-radius: 4px; border: solid 1px black; @@ -714,10 +714,11 @@ h1, #msg-inner { width: 0%; height: 30px; - background-color: #04AA6D; + background-color: black; + transition: width 500ms; } -#load-text { +#load-percent { grid-column: 1; grid-row: 1; @@ -725,6 +726,7 @@ h1, #msg-inner { font-size: 15px; line-height: 30px; color: white; + mix-blend-mode: difference; } #load-spinner { From 56c4d64b2f8cecd07bb0f2c4dac5b65146d6b6a6 Mon Sep 17 00:00:00 2001 From: Grandro Date: Sun, 22 Oct 2023 13:35:08 +0200 Subject: [PATCH 4/9] Added assert statements to validate progress stays <= 100% Declared _curRow as std::atomic in GeomCache.h --- src/qlever-petrimaps/GeomCache.cpp | 16 ++++++++++------ src/qlever-petrimaps/GeomCache.h | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/qlever-petrimaps/GeomCache.cpp b/src/qlever-petrimaps/GeomCache.cpp index c4a667e..e3370b4 100644 --- a/src/qlever-petrimaps/GeomCache.cpp +++ b/src/qlever-petrimaps/GeomCache.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "qlever-petrimaps/GeomCache.h" #include "qlever-petrimaps/Misc.h" #include "qlever-petrimaps/server/Requestor.h" @@ -353,8 +355,9 @@ double GeomCache::getLoadStatusPercent(bool total) { } if (!total) { - return std::atomic(_curRow) / static_cast(_totalSize) * - 100.0; + double percent = _curRow / static_cast(_totalSize) * 100.0; + assert(percent <= 100.0); + return percent; } double parsePercent = 95.0; @@ -362,13 +365,14 @@ double GeomCache::getLoadStatusPercent(bool total) { double totalPercent = 0.0; switch (_loadStatusStage) { case _LoadStatusStages::Parse: - totalPercent = std::atomic(_curRow) / - static_cast(_totalSize) * parsePercent; + totalPercent = _curRow / static_cast(_totalSize) * parsePercent; + assert(totalPercent <= 100.0); break; + case _LoadStatusStages::ParseIds: totalPercent = parsePercent; - totalPercent += std::atomic(_curRow) / - static_cast(_totalSize) * parseIdsPercent; + totalPercent += _curRow / static_cast(_totalSize) * parseIdsPercent; + assert(totalPercent <= 100.0); break; } diff --git a/src/qlever-petrimaps/GeomCache.h b/src/qlever-petrimaps/GeomCache.h index bdaa08b..fe86250 100644 --- a/src/qlever-petrimaps/GeomCache.h +++ b/src/qlever-petrimaps/GeomCache.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "qlever-petrimaps/Misc.h" #include "util/geo/Geo.h" @@ -100,7 +101,8 @@ class GeomCache { uint8_t _curByte; ID _curId; QLEVER_ID_TYPE _maxQid; - size_t _curRow, _curUniqueGeom; + std::atomic _curRow; + size_t _curUniqueGeom; enum _LoadStatusStages {Parse = 1, ParseIds}; _LoadStatusStages _loadStatusStage = Parse; From 521748e801f8b9fc82bff8a08d783db0bc0842d5 Mon Sep 17 00:00:00 2001 From: Grandro Date: Wed, 17 Apr 2024 09:19:17 +0200 Subject: [PATCH 5/9] Handled loading progress for reading cache from disk, added richer descriptions for the loading stages --- src/qlever-petrimaps/GeomCache.cpp | 74 ++++++++++++++++++++------ src/qlever-petrimaps/GeomCache.h | 2 +- src/qlever-petrimaps/PetriMapsMain.cpp | 2 +- src/util | 2 +- web/index.html | 12 +++-- web/merged.js | 8 +-- web/script.js | 27 +++++++--- web/style.css | 12 ++++- 8 files changed, 105 insertions(+), 34 deletions(-) diff --git a/src/qlever-petrimaps/GeomCache.cpp b/src/qlever-petrimaps/GeomCache.cpp index e3370b4..fb0d950 100644 --- a/src/qlever-petrimaps/GeomCache.cpp +++ b/src/qlever-petrimaps/GeomCache.cpp @@ -374,6 +374,11 @@ double GeomCache::getLoadStatusPercent(bool total) { totalPercent += _curRow / static_cast(_totalSize) * parseIdsPercent; assert(totalPercent <= 100.0); break; + + case _LoadStatusStages::FromFile: + totalPercent = _curRow / static_cast(_totalSize) * 100.0; + assert(totalPercent <= 100.0); + break; } return totalPercent; @@ -1005,6 +1010,8 @@ std::string GeomCache::indexHashFromDisk(const std::string& fname) { // _____________________________________________________________________________ void GeomCache::fromDisk(const std::string& fname) { + _loadStatusStage = _LoadStatusStages::FromFile; + _points.clear(); _linePoints.clear(); _lines.clear(); @@ -1015,27 +1022,64 @@ void GeomCache::fromDisk(const std::string& fname) { char tmp[100]; f.read(tmp, 100); tmp[99] = 0; - _indexHash = util::trim(tmp); size_t numPoints; + size_t numLinePoints; + size_t numLines; + size_t numQidToId; + std::streampos posPoints; + std::streampos posLinePoints; + std::streampos posLines; + std::streampos posQidToId; + // get total num points + // points f.read(reinterpret_cast(&numPoints), sizeof(size_t)); _points.resize(numPoints); - f.read(reinterpret_cast(&_points[0]), - sizeof(util::geo::FPoint) * numPoints); - - f.read(reinterpret_cast(&numPoints), sizeof(size_t)); - _linePoints.resize(numPoints); - f.read(reinterpret_cast(&_linePoints[0]), - sizeof(util::geo::Point) * numPoints); - - f.read(reinterpret_cast(&numPoints), sizeof(size_t)); - _lines.resize(numPoints); - f.read(reinterpret_cast(&_lines[0]), sizeof(size_t) * numPoints); + posPoints = f.tellg(); + f.seekg(sizeof(util::geo::FPoint) * numPoints, f.cur); + + // linePoints + f.read(reinterpret_cast(&numLinePoints), sizeof(size_t)); + _linePoints.resize(numLinePoints); + posLinePoints = f.tellg(); + f.seekg(sizeof(util::geo::Point) * numLinePoints, f.cur); + + // lines + f.read(reinterpret_cast(&numLines), sizeof(size_t)); + _lines.resize(numLines); + posLines = f.tellg(); + f.seekg(sizeof(size_t) * numLines, f.cur); + + // qidToId + f.read(reinterpret_cast(&numQidToId), sizeof(size_t)); + _qidToId.resize(numQidToId); + posQidToId = f.tellg(); + f.seekg(sizeof(IdMapping) * numQidToId, f.cur); + + _totalSize = numPoints + numLinePoints + numLines + numQidToId; + _curRow = 0; - f.read(reinterpret_cast(&numPoints), sizeof(size_t)); - _qidToId.resize(numPoints); - f.read(reinterpret_cast(&_qidToId[0]), sizeof(IdMapping) * numPoints); + // read data from file + // points + f.seekg(posPoints); + f.read(reinterpret_cast(&_points[0]), sizeof(util::geo::FPoint) * numPoints); + _curRow += numPoints; + + // linePoints + f.seekg(posLinePoints); + f.read(reinterpret_cast(&_linePoints[0]), sizeof(util::geo::Point) * numLinePoints); + _curRow += numLinePoints; + + // lines + f.seekg(posLines); + f.read(reinterpret_cast(&_lines[0]), sizeof(size_t) * numLines); + _curRow += numLines; + + // qidToId + f.seekg(posQidToId); + f.read(reinterpret_cast(&_qidToId[0]), sizeof(IdMapping) * numQidToId); + _curRow += numQidToId; f.close(); } diff --git a/src/qlever-petrimaps/GeomCache.h b/src/qlever-petrimaps/GeomCache.h index fe86250..442707f 100644 --- a/src/qlever-petrimaps/GeomCache.h +++ b/src/qlever-petrimaps/GeomCache.h @@ -104,7 +104,7 @@ class GeomCache { std::atomic _curRow; size_t _curUniqueGeom; - enum _LoadStatusStages {Parse = 1, ParseIds}; + enum _LoadStatusStages {Parse = 1, ParseIds, FromFile}; _LoadStatusStages _loadStatusStage = Parse; static size_t writeCb(void* contents, size_t size, size_t nmemb, void* userp); diff --git a/src/qlever-petrimaps/PetriMapsMain.cpp b/src/qlever-petrimaps/PetriMapsMain.cpp index 50ecfb3..ca4c61e 100755 --- a/src/qlever-petrimaps/PetriMapsMain.cpp +++ b/src/qlever-petrimaps/PetriMapsMain.cpp @@ -43,7 +43,7 @@ int main(int argc, char** argv) { int cacheLifetime = 6 * 60; double maxMemoryGB = (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE) * 0.9) / 1000000000; - std::string cacheDir; + std::string cacheDir = "Cache"; for (int i = 1; i < argc; i++) { std::string cur = argv[i]; diff --git a/src/util b/src/util index af1ced1..4536392 160000 --- a/src/util +++ b/src/util @@ -1 +1 @@ -Subproject commit af1ced1c82539675b8c9d49c19136224e4af07a9 +Subproject commit 45363920b8803b06b44dc9cae707f375c4495123 diff --git a/web/index.html b/web/index.html index 15a09a7..bbb0291 100755 --- a/web/index.html +++ b/web/index.html @@ -24,11 +24,15 @@
-
Loading results from QLever
-
- +
Loading results from QLever
+
+
Filling the geometry cache
+
This needs to be done only once after the server has been started and does not have to be repeated for subsequent queries.
+
+
+
-
Parsing geometry... (1/2)
+
0.00%
diff --git a/web/merged.js b/web/merged.js index d501608..b07d83f 100644 --- a/web/merged.js +++ b/web/merged.js @@ -131,10 +131,10 @@ function getGeoJsonLayer(geom) { function showError(error) { document.getElementById("msg").style.display = "block"; document.getElementById("loader").style.display = "none"; - document.getElementById("msg-inner").style.color = "red"; - document.getElementById("msg-inner").style.fontWeight = "bold"; - document.getElementById("msg-inner").style.fontSize = "20px"; - document.getElementById("msg-inner").innerHTML = error; + document.getElementById("msg-heading").style.color = "red"; + document.getElementById("msg-heading").style.fontWeight = "bold"; + document.getElementById("msg-heading").style.fontSize = "20px"; + document.getElementById("msg-heading").innerHTML = error; } function loadMap(id, bounds, numObjects) { diff --git a/web/script.js b/web/script.js index 8ab28d2..f82fb20 100755 --- a/web/script.js +++ b/web/script.js @@ -126,12 +126,13 @@ function getGeoJsonLayer(geom) { function showError(error) { error = error.toString(); document.getElementById("msg").style.display = "block"; + document.getElementById("msg-info").style.display = "none"; document.getElementById("load").style.display = "none"; - document.getElementById("msg-inner").style.color = "red"; - document.getElementById("msg-inner").style.fontSize = "20px"; - document.getElementById("msg-inner").innerHTML = error.split("\n")[0]; - if (error.search("\n") > 0) document.getElementById("msg-inner-desc").innerHTML = "
" + error.substring(error.search("\n")) + "
"; - else document.getElementById("msg-inner-desc").innerHTML = ""; + document.getElementById("msg-heading").style.color = "red"; + document.getElementById("msg-heading").style.fontSize = "20px"; + document.getElementById("msg-heading").innerHTML = error.split("\n")[0]; + if (error.search("\n") > 0) document.getElementById("msg-error").innerHTML = "
" + error.substring(error.search("\n")) + "
"; + else document.getElementById("msg-error").innerHTML = ""; } function loadMap(id, bounds, numObjects) { @@ -240,19 +241,32 @@ function loadMap(id, bounds, numObjects) { } function updateLoad(stage, percent) { + const infoElem = document.getElementById("msg-info"); + const infoHeadingElem = document.getElementById("msg-info-heading"); + const infoDescElem = document.getElementById("msg-info-desc"); const stageElem = document.getElementById("load-stage"); const barElem = document.getElementById("load-bar"); const percentElem = document.getElementById("load-percent"); switch (stage) { case 1: + infoHeadingElem.innerHTML = "Filling the geometry cache"; + infoDescElem.innerHTML = "This needs to be done only once for each new version of the dataset and does not have to be repeated for subsequent queries."; stageElem.innerHTML = "Parsing geometry... (1/2)"; break; case 2: + infoHeadingElem.innerHTML = "Filling the geometry cache"; + infoDescElem.innerHTML = "This needs to be done only once for each new version of the dataset and does not have to be repeated for subsequent queries."; stageElem.innerHTML = "Parsing geometry Ids... (2/2)"; break; + case 3: + infoHeadingElem.innerHTML = "Reading cached geometries from disk"; + infoDescElem.innerHTML = "This needs to be done only once after the server has been started and does not have to be repeated for subsequent queries."; + stageElem.innerHTML = "Loading from disk... (1/1)"; + break; } barElem.style.width = percent + "%"; percentElem.innerHTML = percent.toString() + "%"; + infoElem.style.display = "block"; } function fetchResults() { @@ -277,7 +291,7 @@ function fetchLoadStatusInterval(interval) { } async function fetchLoadStatus() { - console.log("Fetching load status..."); + //console.log("Fetching load status..."); fetch('loadstatus?backend=' + qleverBackend) .then(response => { @@ -288,6 +302,7 @@ async function fetchLoadStatus() { .then(data => { var stage = data["stage"]; var percent = parseFloat(data["percent"]).toFixed(2); + console.log("Fetched load status: stage: ", stage, ", ", percent, "%"); updateLoad(stage, percent); }) .catch(error => { diff --git a/web/style.css b/web/style.css index 8ecdab4..cf2621d 100755 --- a/web/style.css +++ b/web/style.css @@ -630,7 +630,7 @@ tt { margin: 5px -10px 5px -6px; } -h1, #msg-inner { +h1, #msg-heading { font: 1.5em sans-serif; margin-bottom: 40px; opacity: 0.8; @@ -650,7 +650,15 @@ h1, #msg-inner { padding: 50px; } -#msg-inner-desc { +#msg-info-heading { + font-weight: bold; +} + +#msg-info-desc { + margin-bottom: 40px; +} + +#msg-error { font: 0.9em mono; text-align: left; background-color: #fefefe; From 85a2f78ef680c6a482f3284445a429a83ec755ae Mon Sep 17 00:00:00 2001 From: Grandro Date: Wed, 17 Apr 2024 10:28:33 +0200 Subject: [PATCH 6/9] Included number of geometries in loading message --- src/qlever-petrimaps/GeomCache.cpp | 10 ++++++++++ src/qlever-petrimaps/GeomCache.h | 17 +++++++---------- src/qlever-petrimaps/server/Server.cpp | 6 +++++- web/script.js | 17 +++++++++-------- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/qlever-petrimaps/GeomCache.cpp b/src/qlever-petrimaps/GeomCache.cpp index fb0d950..366847a 100644 --- a/src/qlever-petrimaps/GeomCache.cpp +++ b/src/qlever-petrimaps/GeomCache.cpp @@ -389,6 +389,16 @@ int GeomCache::getLoadStatusStage() { return _loadStatusStage; } +// _____________________________________________________________________________ +size_t GeomCache::getTotalProgress() { + return _totalSize; +} + +// _____________________________________________________________________________ +size_t GeomCache::getCurrentProgress() { + return _curRow; +} + // _____________________________________________________________________________ void GeomCache::parseIds(const char* c, size_t size) { _loadStatusStage = _LoadStatusStages::ParseIds; diff --git a/src/qlever-petrimaps/GeomCache.h b/src/qlever-petrimaps/GeomCache.h index 442707f..b26f7e2 100644 --- a/src/qlever-petrimaps/GeomCache.h +++ b/src/qlever-petrimaps/GeomCache.h @@ -25,8 +25,7 @@ class GeomCache { GeomCache() : _backendUrl(""), _curl(0) {} explicit GeomCache(const std::string& backendUrl) : _backendUrl(backendUrl), - _curl(curl_easy_init()) { - } + _curl(curl_easy_init()) {} GeomCache& operator=(GeomCache&& o) { _backendUrl = o._backendUrl; @@ -93,6 +92,8 @@ class GeomCache { double getLoadStatusPercent(bool total); double getLoadStatusPercent() { return getLoadStatusPercent(false); }; int getLoadStatusStage(); + size_t getTotalProgress(); + size_t getCurrentProgress(); private: std::string _backendUrl; @@ -101,6 +102,7 @@ class GeomCache { uint8_t _curByte; ID _curId; QLEVER_ID_TYPE _maxQid; + size_t _totalSize = 0; std::atomic _curRow; size_t _curUniqueGeom; @@ -108,12 +110,9 @@ class GeomCache { _LoadStatusStages _loadStatusStage = Parse; static size_t writeCb(void* contents, size_t size, size_t nmemb, void* userp); - static size_t writeCbIds(void* contents, size_t size, size_t nmemb, - void* userp); - static size_t writeCbCount(void* contents, size_t size, size_t nmemb, - void* userp); - static size_t writeCbString(void* contents, size_t size, size_t nmemb, - void* userp); + static size_t writeCbIds(void* contents, size_t size, size_t nmemb, void* userp); + static size_t writeCbCount(void* contents, size_t size, size_t nmemb, void* userp); + static size_t writeCbString(void* contents, size_t size, size_t nmemb, void* userp); // Get the right SPARQL query for the given backend. const std::string& getQuery(const std::string& backendUrl) const; @@ -147,8 +146,6 @@ class GeomCache { std::fstream _linesF; std::fstream _qidToIdF; - size_t _totalSize = 0; - IdMapping _lastQidToId; std::vector _qidToId; diff --git a/src/qlever-petrimaps/server/Server.cpp b/src/qlever-petrimaps/server/Server.cpp index 4c2a0c7..cefbbb9 100755 --- a/src/qlever-petrimaps/server/Server.cpp +++ b/src/qlever-petrimaps/server/Server.cpp @@ -1128,10 +1128,14 @@ util::http::Answer Server::handleLoadStatusReq(const Params& pars) const { std::shared_ptr cache = _caches[backend]; double loadStatusPercent = cache->getLoadStatusPercent(true); int loadStatusStage = cache->getLoadStatusStage(); + size_t totalProgress = cache->getTotalProgress(); + size_t currentProgress = cache->getCurrentProgress(); std::stringstream json; json << "{\"percent\": " << loadStatusPercent - << ", \"stage\": " << loadStatusStage << "}"; + << ", \"stage\": " << loadStatusStage + << ", \"totalProgress\": " << totalProgress + << ", \"currentProgress\": " << currentProgress << "}"; util::http::Answer ans = util::http::Answer("200 OK", json.str()); return ans; diff --git a/web/script.js b/web/script.js index f82fb20..81af6a4 100755 --- a/web/script.js +++ b/web/script.js @@ -240,7 +240,7 @@ function loadMap(id, bounds, numObjects) { }); } -function updateLoad(stage, percent) { +function updateLoad(stage, percent, totalProgress, currentProgress) { const infoElem = document.getElementById("msg-info"); const infoHeadingElem = document.getElementById("msg-info-heading"); const infoDescElem = document.getElementById("msg-info-desc"); @@ -251,17 +251,17 @@ function updateLoad(stage, percent) { case 1: infoHeadingElem.innerHTML = "Filling the geometry cache"; infoDescElem.innerHTML = "This needs to be done only once for each new version of the dataset and does not have to be repeated for subsequent queries."; - stageElem.innerHTML = "Parsing geometry... (1/2)"; + stageElem.innerHTML = `Parsing ${currentProgress}/${totalProgress} geometries... (1/2)`; break; case 2: infoHeadingElem.innerHTML = "Filling the geometry cache"; infoDescElem.innerHTML = "This needs to be done only once for each new version of the dataset and does not have to be repeated for subsequent queries."; - stageElem.innerHTML = "Parsing geometry Ids... (2/2)"; + stageElem.innerHTML = `Fetching ${currentProgress}/${totalProgress} geometries... (2/2)`; break; case 3: infoHeadingElem.innerHTML = "Reading cached geometries from disk"; infoDescElem.innerHTML = "This needs to be done only once after the server has been started and does not have to be repeated for subsequent queries."; - stageElem.innerHTML = "Loading from disk... (1/1)"; + stageElem.innerHTML = `Reading ${currentProgress}/${totalProgress} geometries from disk... (1/1)`; break; } barElem.style.width = percent + "%"; @@ -291,19 +291,20 @@ function fetchLoadStatusInterval(interval) { } async function fetchLoadStatus() { - //console.log("Fetching load status..."); + console.log("Fetching load status..."); fetch('loadstatus?backend=' + qleverBackend) .then(response => { if (!response.ok) return response.text().then(text => {throw new Error(text)}); return response; - }) + }) .then(response => response.json()) .then(data => { var stage = data["stage"]; var percent = parseFloat(data["percent"]).toFixed(2); - console.log("Fetched load status: stage: ", stage, ", ", percent, "%"); - updateLoad(stage, percent); + var totalProgress = data["totalProgress"].toLocaleString('en'); + var currentProgress = data["currentProgress"].toLocaleString('en'); + updateLoad(stage, percent, totalProgress, currentProgress); }) .catch(error => { showError(error); From 17a75b37f22011bb34a50a971b830208c092ce02 Mon Sep 17 00:00:00 2001 From: Grandro Date: Tue, 23 Apr 2024 20:28:06 +0200 Subject: [PATCH 7/9] Added loading progress in Server for image generation --- src/qlever-petrimaps/server/Server.cpp | 50 ++++++++++++++++++---- src/qlever-petrimaps/server/Server.h | 9 +++- web/script.js | 59 ++++++++++++++------------ 3 files changed, 80 insertions(+), 38 deletions(-) diff --git a/src/qlever-petrimaps/server/Server.cpp b/src/qlever-petrimaps/server/Server.cpp index cefbbb9..0cc2ff2 100755 --- a/src/qlever-petrimaps/server/Server.cpp +++ b/src/qlever-petrimaps/server/Server.cpp @@ -47,6 +47,7 @@ using util::geo::LineSegment; using util::geo::webMercToLatLng; const static double THRESHOLD = 200; +static std::atomic _curRow; // _____________________________________________________________________________ Server::Server(size_t maxMemory, const std::string& cacheDir, int cacheLifetime) @@ -146,7 +147,6 @@ util::http::Answer Server::handleHeatMapReq(const Params& pars, if (box.size() != 4) throw std::invalid_argument("Invalid request."); std::shared_ptr r; - { std::lock_guard guard(_m); bool has = _rs.count(id); @@ -288,7 +288,6 @@ util::http::Answer Server::handleHeatMapReq(const Params& pars, } // LINES - const auto& lgrid = r->getLineGrid(); if (intersects(lgrid.getBBox(), fbbox)) { @@ -655,7 +654,6 @@ util::http::Answer Server::handlePosReq(const Params& pars) const { LOG(INFO) << "[SERVER] Click at " << x << ", " << y; std::shared_ptr reqor; - { std::lock_guard guard(_m); bool has = _rs.count(id); @@ -863,6 +861,10 @@ std::string Server::parseUrl(std::string u, std::string pl, return util::urlDecode(parts.front()); } +void Server::pngWriteRowCb(png_structp png_ptr, png_uint_32 row, int pass) { + _curRow = row; +} + // _____________________________________________________________________________ inline void pngWriteCb(png_structp png_ptr, png_bytep data, png_size_t length) { int sock = *((int*)png_get_io_ptr(png_ptr)); @@ -891,7 +893,7 @@ inline void pngErrorCb(png_structp, png_const_charp error_msg) { } // _____________________________________________________________________________ -void Server::writePNG(const unsigned char* data, size_t w, size_t h, int sock) { +void Server::writePNG(const unsigned char* data, size_t w, size_t h, int sock) const { png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, pngErrorCb, pngWarnCb); if (!png_ptr) return; @@ -907,8 +909,12 @@ void Server::writePNG(const unsigned char* data, size_t w, size_t h, int sock) { return; } - png_set_write_fn(png_ptr, &sock, pngWriteCb, 0); + // Handle Load Status + _totalSize = h; + _curRow = 0; + png_set_write_status_fn(png_ptr, pngWriteRowCb); + png_set_write_fn(png_ptr, &sock, pngWriteCb, 0); png_set_filter(png_ptr, 0, PNG_FILTER_NONE | PNG_FILTER_VALUE_NONE); png_set_compression_level(png_ptr, 7); @@ -918,8 +924,7 @@ void Server::writePNG(const unsigned char* data, size_t w, size_t h, int sock) { png_set_IHDR(png_ptr, info_ptr, w, h, bit_depth, color_type, interlace_type, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - png_bytep* row_pointers = - (png_byte**)png_malloc(png_ptr, h * sizeof(png_bytep)); + png_bytep* row_pointers = (png_byte**)png_malloc(png_ptr, h * sizeof(png_bytep)); for (size_t y = 0; y < h; ++y) { row_pointers[y] = const_cast(data + y * w * 4); @@ -1126,13 +1131,27 @@ util::http::Answer Server::handleLoadStatusReq(const Params& pars) const { auto backend = pars.find("backend")->second; createCache(backend); std::shared_ptr cache = _caches[backend]; - double loadStatusPercent = cache->getLoadStatusPercent(true); + + // We have 3 loading stages: + // 1) Filling geometry cache / reading cache from disk + // 2) Fetching geometries + // 3) Rendering result + // 1) + 2) by GeomCache, 3) by Server + // => Merge load status + // 1) + 2) = 95%, 3) = 5% + + double geomCachePercent = 0.95; + double serverPercent = 0.05; + double geomCacheLoadStatusPercent = cache->getLoadStatusPercent(true); + double serverLoadStatusPercent = getLoadStatusPercent(); + double totalPercent = geomCachePercent * geomCacheLoadStatusPercent + serverPercent * serverLoadStatusPercent; + int loadStatusStage = cache->getLoadStatusStage(); size_t totalProgress = cache->getTotalProgress(); size_t currentProgress = cache->getCurrentProgress(); std::stringstream json; - json << "{\"percent\": " << loadStatusPercent + json << "{\"percent\": " << totalPercent << ", \"stage\": " << loadStatusStage << ", \"totalProgress\": " << totalProgress << ", \"currentProgress\": " << currentProgress << "}"; @@ -1173,6 +1192,19 @@ std::string Server::getSessionId() const { return std::to_string(d(rng)); } +double Server::getLoadStatusPercent() const { + if (_totalSize == 0) { + return 0.0; + } + + double percent = _curRow / static_cast(_totalSize) * 100.0; + LOG(INFO) << "[SERVER] _curRow:\n" << _curRow; + LOG(INFO) << "[SERVER] _totalSize:\n" << _totalSize; + assert(percent <= 100.0); + + return percent; +} + void Server::createCache(const std::string& backend) const { std::shared_ptr cache; diff --git a/src/qlever-petrimaps/server/Server.h b/src/qlever-petrimaps/server/Server.h index 14e0a5f..8139dbe 100755 --- a/src/qlever-petrimaps/server/Server.h +++ b/src/qlever-petrimaps/server/Server.h @@ -11,6 +11,7 @@ #include #include +#include #include "qlever-petrimaps/GeomCache.h" #include "qlever-petrimaps/server/Requestor.h" #include "util/http/Server.h" @@ -51,7 +52,10 @@ class Server : public util::http::Handler { std::string getSessionId() const; - static void writePNG(const unsigned char* data, size_t w, size_t h, int sock); + double getLoadStatusPercent() const; + + static void pngWriteRowCb(png_structp png_ptr, png_uint_32 row, int pass); + void writePNG(const unsigned char* data, size_t w, size_t h, int sock) const; void drawPoint(std::vector& points, std::vector& points2, int px, int py, int w, int h, MapStyle style) const; @@ -64,6 +68,9 @@ class Server : public util::http::Handler { int _cacheLifetime; + // Load Status + mutable size_t _totalSize = 0; + mutable std::mutex _m; mutable std::map> _caches; diff --git a/web/script.js b/web/script.js index 81af6a4..381bcab 100755 --- a/web/script.js +++ b/web/script.js @@ -136,7 +136,6 @@ function showError(error) { } function loadMap(id, bounds, numObjects) { - document.getElementById("msg").style.display = "none"; const ll = L.Projection.SphericalMercator.unproject({"x": bounds[0][0], "y":bounds[0][1]}); const ur = L.Projection.SphericalMercator.unproject({"x": bounds[1][0], "y":bounds[1][1]}); const boundsLatLng = [[ll.lat, ll.lng], [ur.lat, ur.lng]]; @@ -163,41 +162,40 @@ function loadMap(id, bounds, numObjects) { format: 'image/png' }); - const autoLayer = L.layerGroup([ - L.nonTiledLayer.wms('heatmap', { - minZoom: 0, - maxZoom: 15, - opacity: 0.8, - layers: id, - styles: ["heatmap"], - format: 'image/png', - transparent: true, - }), L.nonTiledLayer.wms('heatmap', { - minZoom: 16, - maxZoom: 19, - layers: id, - styles: ["objects"], - format: 'image/png' - }) - ]); + const autoHeatmapLayer = L.nonTiledLayer.wms('heatmap', { + minZoom: 0, + maxZoom: 15, + opacity: 0.8, + layers: id, + styles: ["heatmap"], + format: 'image/png', + transparent: true, + }); - heatmapLayer.on('error', function() {showError(genError);}); - objectsLayer.on('error', function() {showError(genError);}); - heatmapLayer.on('load', function() {console.log("Finished loading map!");}); - objectsLayer.on('load', function() {console.log("Finished loading map!");}); - autoLayer.on('error', function() {showError(genError);}); - autoLayer.on('load', function() {console.log("Finished loading map!");}); + const autoObjectLayer = L.nonTiledLayer.wms('heatmap', { + minZoom: 16, + maxZoom: 19, + layers: id, + styles: ["objects"], + format: 'image/png' + }); + const autoLayerGroup = L.layerGroup([autoHeatmapLayer, autoObjectLayer]); + + heatmapLayer.on('load', _onLayerLoad); + objectsLayer.on('load', _onLayerLoad); + autoHeatmapLayer.on('load', _onLayerLoad); + autoObjectLayer.on('load', _onLayerLoad); layerControl.addBaseLayer(heatmapLayer, "Heatmap"); layerControl.addBaseLayer(objectsLayer, "Objects"); - layerControl.addBaseLayer(autoLayer, "Auto"); + layerControl.addBaseLayer(autoLayerGroup, "Auto"); if (mode == "heatmap") { heatmapLayer.addTo(map).on('error', function() {showError(genError);}); } else if (mode == "objects") { objectsLayer.addTo(map).on('error', function() {showError(genError);}); } else { - autoLayer.addTo(map).on('error', function() {showError(genError);}); + autoLayerGroup.addTo(map).on('error', function() {showError(genError);}); } map.on('click', function(e) { @@ -215,7 +213,6 @@ function loadMap(id, bounds, numObjects) { let styles = "objects"; if (map.hasLayer(heatmapLayer)) styles = "heatmap"; if (map.hasLayer(objectsLayer)) styles = "objects"; - if (map.hasLayer(objectsLayer)) styles = "objects"; fetch('pos?x=' + pos.x + "&y=" + pos.y + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom())) + '&width=' + w + '&height=' + h + '&bbox=' + bounds.join(',') + '&styles=' + styles) .then(response => { @@ -279,7 +276,6 @@ function fetchResults() { }) .then(response => response.json()) .then(data => { - clearInterval(loadStatusIntervalId); loadMap(data["qid"], data["bounds"], data["numobjects"]); }) .catch(error => showError(error)); @@ -315,6 +311,13 @@ async function fetchLoadStatus() { fetchResults(); fetchLoadStatusInterval(333); +function _onLayerLoad(e) { + console.log("Map finished loading."); + clearInterval(loadStatusIntervalId); + + document.getElementById("msg").style.display = "none"; +} + document.getElementById("ex-geojson").onclick = function() { if (!sessionId) return; let a = document.createElement("a"); From 835df9dccdd5d4f7b3fe8af217ed4422c2feb98d Mon Sep 17 00:00:00 2001 From: Grandro Date: Thu, 25 Apr 2024 17:17:15 +0200 Subject: [PATCH 8/9] Improved reading from disk progress by reading line by line --- src/qlever-petrimaps/GeomCache.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/qlever-petrimaps/GeomCache.cpp b/src/qlever-petrimaps/GeomCache.cpp index 366847a..b3ebee5 100644 --- a/src/qlever-petrimaps/GeomCache.cpp +++ b/src/qlever-petrimaps/GeomCache.cpp @@ -1073,23 +1073,31 @@ void GeomCache::fromDisk(const std::string& fname) { // read data from file // points f.seekg(posPoints); - f.read(reinterpret_cast(&_points[0]), sizeof(util::geo::FPoint) * numPoints); - _curRow += numPoints; + for (size_t i = 0; i < numPoints; i++) { + f.read(reinterpret_cast(&_points[i]), sizeof(util::geo::FPoint)); + _curRow += 1; + } // linePoints f.seekg(posLinePoints); - f.read(reinterpret_cast(&_linePoints[0]), sizeof(util::geo::Point) * numLinePoints); - _curRow += numLinePoints; + for (size_t i = 0; i < numLinePoints; i++) { + f.read(reinterpret_cast(&_linePoints[i]), sizeof(util::geo::Point)); + _curRow += 1; + } // lines f.seekg(posLines); - f.read(reinterpret_cast(&_lines[0]), sizeof(size_t) * numLines); - _curRow += numLines; + for (size_t i = 0; i < numLines; i++) { + f.read(reinterpret_cast(&_lines[i]), sizeof(size_t)); + _curRow += 1; + } // qidToId f.seekg(posQidToId); - f.read(reinterpret_cast(&_qidToId[0]), sizeof(IdMapping) * numQidToId); - _curRow += numQidToId; + for (size_t i = 0; i < numQidToId; i++) { + f.read(reinterpret_cast(&_qidToId[i]), sizeof(IdMapping)); + _curRow += 1; + } f.close(); } From d3b069d38e55443161dbe602025be82f0b75c6a7 Mon Sep 17 00:00:00 2001 From: Grandro Date: Wed, 1 May 2024 17:59:22 +0200 Subject: [PATCH 9/9] Reverted some debugging stuff. --- src/qlever-petrimaps/PetriMapsMain.cpp | 2 +- src/qlever-petrimaps/server/Server.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/qlever-petrimaps/PetriMapsMain.cpp b/src/qlever-petrimaps/PetriMapsMain.cpp index ca4c61e..50ecfb3 100755 --- a/src/qlever-petrimaps/PetriMapsMain.cpp +++ b/src/qlever-petrimaps/PetriMapsMain.cpp @@ -43,7 +43,7 @@ int main(int argc, char** argv) { int cacheLifetime = 6 * 60; double maxMemoryGB = (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE) * 0.9) / 1000000000; - std::string cacheDir = "Cache"; + std::string cacheDir; for (int i = 1; i < argc; i++) { std::string cur = argv[i]; diff --git a/src/qlever-petrimaps/server/Server.cpp b/src/qlever-petrimaps/server/Server.cpp index 0cc2ff2..171a8a2 100755 --- a/src/qlever-petrimaps/server/Server.cpp +++ b/src/qlever-petrimaps/server/Server.cpp @@ -1198,8 +1198,6 @@ double Server::getLoadStatusPercent() const { } double percent = _curRow / static_cast(_totalSize) * 100.0; - LOG(INFO) << "[SERVER] _curRow:\n" << _curRow; - LOG(INFO) << "[SERVER] _totalSize:\n" << _totalSize; assert(percent <= 100.0); return percent;