@panoramax/web-viewer 3.1.1-develop-1e642517 → 3.1.1-develop-a3fa5272
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/build/index.css +1 -1
- package/build/index.css.map +1 -1
- package/build/index.js +5 -5
- package/build/index.js.map +1 -1
- package/docs/03_URL_settings.md +10 -1
- package/package.json +4 -3
- package/src/Viewer.js +33 -2
- package/src/components/CoreView.css +6 -0
- package/src/components/CoreView.js +25 -10
- package/src/components/Map.js +9 -0
- package/src/translations/en.json +12 -2
- package/src/translations/fr.json +12 -2
- package/src/translations/hu.json +105 -70
- package/src/utils/API.js +20 -3
- package/src/utils/Exif.js +3 -8
- package/src/utils/Map.js +20 -1
- package/src/utils/Utils.js +36 -0
- package/src/utils/Widgets.js +47 -0
- package/src/viewer/URLHash.js +16 -1
- package/src/viewer/Widgets.css +63 -0
- package/src/viewer/Widgets.js +111 -29
- package/tests/utils/Exif.test.js +10 -10
- package/tests/utils/__snapshots__/API.test.js.snap +5 -0
- package/tests/viewer/__snapshots__/Widgets.test.js.snap +39 -29
package/src/utils/Map.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// DO NOT REMOVE THE "!": bundled builds breaks otherwise !!!
|
|
2
2
|
import maplibregl from "!maplibre-gl";
|
|
3
3
|
import LoaderImg from "../img/marker.svg";
|
|
4
|
-
import { COLORS } from "./Utils";
|
|
4
|
+
import { COLORS, QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_RES_360_VALUES, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_POND_RES, QUALITYSCORE_POND_GPS } from "./Utils";
|
|
5
5
|
import { autoDetectLocale } from "./I18n";
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_TILES = "https://panoramax.openstreetmap.fr/pmtiles/basic.json";
|
|
@@ -63,6 +63,24 @@ export const VECTOR_STYLES = {
|
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
|
|
67
|
+
// See MapLibre docs for explanation of expressions magic: https://maplibre.org/maplibre-style-spec/expressions/
|
|
68
|
+
const MAP_EXPR_QUALITYSCORE_RES_360 = ["case", ["has", "h_pixel_density"], ["step", ["get", "h_pixel_density"], ...QUALITYSCORE_RES_360_VALUES], 1];
|
|
69
|
+
const MAP_EXPR_QUALITYSCORE_RES_FLAT = ["case", ["has", "h_pixel_density"], ["step", ["get", "h_pixel_density"], ...QUALITYSCORE_RES_FLAT_VALUES], 1];
|
|
70
|
+
const MAP_EXPR_QUALITYSCORE_RES = [
|
|
71
|
+
"case", ["==", ["get", "type"], "equirectangular"],
|
|
72
|
+
MAP_EXPR_QUALITYSCORE_RES_360, MAP_EXPR_QUALITYSCORE_RES_FLAT
|
|
73
|
+
];
|
|
74
|
+
const MAP_EXPR_QUALITYSCORE_GPS = ["case", ["has", "gps_accuracy"], ["step", ["get", "gps_accuracy"], ...QUALITYSCORE_GPS_VALUES], 1];
|
|
75
|
+
// Note: score is also calculated in widgets/popup code
|
|
76
|
+
export const MAP_EXPR_QUALITYSCORE = [
|
|
77
|
+
"round",
|
|
78
|
+
["+",
|
|
79
|
+
["*", MAP_EXPR_QUALITYSCORE_RES, QUALITYSCORE_POND_RES],
|
|
80
|
+
["*", MAP_EXPR_QUALITYSCORE_GPS, QUALITYSCORE_POND_GPS]]
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
|
|
66
84
|
/**
|
|
67
85
|
* Get the GIF shown while thumbnail loads
|
|
68
86
|
* @param {object} lang Translations
|
|
@@ -114,6 +132,7 @@ export function combineStyles(parent, options) {
|
|
|
114
132
|
// Complementary style
|
|
115
133
|
if(options.supplementaryStyle) {
|
|
116
134
|
Object.assign(style.sources, options.supplementaryStyle.sources || {});
|
|
135
|
+
Object.assign(style.metadata, options.supplementaryStyle.metadata || {});
|
|
117
136
|
style.layers = style.layers.concat(options.supplementaryStyle.layers || []);
|
|
118
137
|
}
|
|
119
138
|
|
package/src/utils/Utils.js
CHANGED
|
@@ -23,9 +23,45 @@ export const COLORS_HEX = Object.fromEntries(Object.entries(COLORS).map(e => {
|
|
|
23
23
|
return e;
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
+
export const QUALITYSCORE_VALUES = [
|
|
27
|
+
{ color: "#007f4e", label: "A" },
|
|
28
|
+
{ color: "#72b043", label: "B" },
|
|
29
|
+
{ color: "#b5be2f", label: "C" },
|
|
30
|
+
{ color: "#f8cc1b", label: "D" },
|
|
31
|
+
{ color: "#f6a020", label: "E" },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const QUALITYSCORE_RES_FLAT_VALUES = [1, 15, 2, 38, 3, 60, 4]; // Grade, < Px/FOV value
|
|
35
|
+
export const QUALITYSCORE_RES_360_VALUES = [2, 15, 3, 20, 4, 38, 5]; // Grade, < Px/FOV value
|
|
36
|
+
export const QUALITYSCORE_GPS_VALUES = [5, 1.01, 4, 2.01, 3, 5.01, 2, 10.01, 1]; // Grade, < Meters value
|
|
37
|
+
export const QUALITYSCORE_POND_RES = 4/5;
|
|
38
|
+
export const QUALITYSCORE_POND_GPS = 1/5;
|
|
39
|
+
|
|
26
40
|
const ArrowTriangle = svgToPSVLink(ArrowTriangleSVG, "white");
|
|
27
41
|
const ArrowTurn = svgToPSVLink(ArrowTurnSVG, COLORS.NEXT);
|
|
28
42
|
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Find the grade associated to an input Quality Score definition.
|
|
46
|
+
* @param {number[]} ranges The QUALITYSCORE_*_VALUES definition
|
|
47
|
+
* @param {number} value The picture value
|
|
48
|
+
* @return {number} The corresponding grade (1 to 5, or null if missing)
|
|
49
|
+
*/
|
|
50
|
+
export function getGrade(ranges, value) {
|
|
51
|
+
if(value === null || value === undefined || value === "") { return null; }
|
|
52
|
+
|
|
53
|
+
// Read each pair from table (grade, reference value)
|
|
54
|
+
for(let i = 0; i < ranges.length; i += 2) {
|
|
55
|
+
const grade = ranges[i];
|
|
56
|
+
const limit = ranges[i+1];
|
|
57
|
+
|
|
58
|
+
// Send grade if value is under limit
|
|
59
|
+
if (value < limit) { return grade;}
|
|
60
|
+
}
|
|
61
|
+
// Otherwise, send last grade
|
|
62
|
+
return ranges[ranges.length - 1];
|
|
63
|
+
}
|
|
64
|
+
|
|
29
65
|
/**
|
|
30
66
|
* Get cartesian distance between two points
|
|
31
67
|
* @param {number[]} from Start [x,y] coordinates
|
package/src/utils/Widgets.js
CHANGED
|
@@ -7,6 +7,9 @@ import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons/faMagnifyin
|
|
|
7
7
|
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
|
|
8
8
|
import { faCheck } from "@fortawesome/free-solid-svg-icons/faCheck";
|
|
9
9
|
import { faCopy } from "@fortawesome/free-solid-svg-icons/faCopy";
|
|
10
|
+
import { faStar } from "@fortawesome/free-solid-svg-icons/faStar";
|
|
11
|
+
import { faStar as farStar } from "@fortawesome/free-regular-svg-icons/faStar";
|
|
12
|
+
import { QUALITYSCORE_VALUES } from "./Utils";
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -479,3 +482,47 @@ export function createLabel(forAttr, text, faIcon = null) {
|
|
|
479
482
|
label.appendChild(document.createTextNode(text));
|
|
480
483
|
return label;
|
|
481
484
|
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Show a grade in a nice, user-friendly way
|
|
488
|
+
* @param {number} grade The obtained grade
|
|
489
|
+
* @returns {string} Nice to display grade display
|
|
490
|
+
*/
|
|
491
|
+
export function showGrade(grade, t) {
|
|
492
|
+
let label = "<span class=\"gvs-grade\">";
|
|
493
|
+
|
|
494
|
+
for(let i=1; i <= grade; i++) {
|
|
495
|
+
label += fat(faStar);
|
|
496
|
+
}
|
|
497
|
+
for(let i=grade+1; i <= 5; i++) {
|
|
498
|
+
label += fat(farStar);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
label += "</span> (";
|
|
502
|
+
if(grade === null) { label += t.gvs.metadata_quality_missing+")"; }
|
|
503
|
+
else { label += grade + "/5)"; }
|
|
504
|
+
return label;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Displays a nice QualityScore
|
|
509
|
+
* @param {number} grade The 1 to 5 grade
|
|
510
|
+
* @returns {Element} The HTML code for showing the grade
|
|
511
|
+
*/
|
|
512
|
+
export function showQualityScore(grade) {
|
|
513
|
+
const span = document.createElement("span");
|
|
514
|
+
|
|
515
|
+
for(let i=1; i <= QUALITYSCORE_VALUES.length; i++) {
|
|
516
|
+
const pv = QUALITYSCORE_VALUES[i-1];
|
|
517
|
+
const sub = document.createElement("span");
|
|
518
|
+
sub.appendChild(document.createTextNode(pv.label));
|
|
519
|
+
sub.classList.add("gvs-qualityscore");
|
|
520
|
+
sub.style.backgroundColor = pv.color;
|
|
521
|
+
if(i === (6-grade)) {
|
|
522
|
+
sub.classList.add("gvs-qualityscore-selected");
|
|
523
|
+
}
|
|
524
|
+
span.appendChild(sub);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return span;
|
|
528
|
+
}
|
package/src/viewer/URLHash.js
CHANGED
|
@@ -4,6 +4,7 @@ const MAP_FILTERS_JS2URL = {
|
|
|
4
4
|
"type": "pic_type",
|
|
5
5
|
"camera": "camera",
|
|
6
6
|
"theme": "theme",
|
|
7
|
+
"qualityscore": "pic_score",
|
|
7
8
|
};
|
|
8
9
|
const MAP_FILTERS_URL2JS = Object.fromEntries(Object.entries(MAP_FILTERS_JS2URL).map(v => [v[1], v[0]]));
|
|
9
10
|
const UPDATE_HASH_EVENTS = [
|
|
@@ -98,6 +99,10 @@ export default class URLHash extends EventTarget {
|
|
|
98
99
|
hashParts[MAP_FILTERS_JS2URL[k]] = this._viewer._mapFilters[k];
|
|
99
100
|
}
|
|
100
101
|
}
|
|
102
|
+
if(hashParts.pic_score) {
|
|
103
|
+
const mapping = [null, "E", "D", "C", "B", "A"];
|
|
104
|
+
hashParts.pic_score = hashParts.pic_score.map(v => mapping[v]).join("");
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
else {
|
|
@@ -164,7 +169,7 @@ export default class URLHash extends EventTarget {
|
|
|
164
169
|
|
|
165
170
|
keyvals = {};
|
|
166
171
|
|
|
167
|
-
// Used letters: b c d e f k m n p s t u v
|
|
172
|
+
// Used letters: b c d e f k m n p q s t u v
|
|
168
173
|
// Focus
|
|
169
174
|
if(shortVals.f === "m") { keyvals.focus = "map"; }
|
|
170
175
|
else if(shortVals.f === "p") { keyvals.focus = "pic"; }
|
|
@@ -202,6 +207,7 @@ export default class URLHash extends EventTarget {
|
|
|
202
207
|
if(shortVals.v === "d") { keyvals.theme = "default"; }
|
|
203
208
|
else if(shortVals.v === "a") { keyvals.theme = "age"; }
|
|
204
209
|
else if(shortVals.v === "t") { keyvals.theme = "type"; }
|
|
210
|
+
else if(shortVals.v === "s") { keyvals.theme = "score"; }
|
|
205
211
|
|
|
206
212
|
// Background
|
|
207
213
|
if(shortVals.b === "s") { keyvals.background = "streets"; }
|
|
@@ -209,6 +215,9 @@ export default class URLHash extends EventTarget {
|
|
|
209
215
|
|
|
210
216
|
// Users
|
|
211
217
|
if(shortVals.u !== "") { keyvals.users = shortVals.u; }
|
|
218
|
+
|
|
219
|
+
// Photoscore
|
|
220
|
+
if(shortVals.q !== "") { keyvals.pic_score = shortVals.q; }
|
|
212
221
|
}
|
|
213
222
|
|
|
214
223
|
return keyvals;
|
|
@@ -332,6 +341,7 @@ export default class URLHash extends EventTarget {
|
|
|
332
341
|
v: (hashParts.theme || "").substring(0, 1),
|
|
333
342
|
b: (hashParts.background || "").substring(0, 1),
|
|
334
343
|
u: hashParts.users,
|
|
344
|
+
q: hashParts.pic_score,
|
|
335
345
|
};
|
|
336
346
|
const short = Object.entries(shortVals)
|
|
337
347
|
.filter(([,v]) => v != undefined && v != "")
|
|
@@ -353,6 +363,11 @@ export default class URLHash extends EventTarget {
|
|
|
353
363
|
newMapFilters[MAP_FILTERS_URL2JS[k]] = vals[k];
|
|
354
364
|
}
|
|
355
365
|
}
|
|
366
|
+
if(newMapFilters.qualityscore) {
|
|
367
|
+
let values = newMapFilters.qualityscore.split("");
|
|
368
|
+
const mapping = {"A": 5, "B": 4, "C": 3, "D": 2, "E": 1};
|
|
369
|
+
newMapFilters.qualityscore = values.map(v => mapping[v]);
|
|
370
|
+
}
|
|
356
371
|
return newMapFilters;
|
|
357
372
|
}
|
|
358
373
|
|
package/src/viewer/Widgets.css
CHANGED
|
@@ -455,6 +455,11 @@ a.gvs-btn { text-decoration: none; }
|
|
|
455
455
|
margin-right: 5px;
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
/* Grades */
|
|
459
|
+
.gvs-grade {
|
|
460
|
+
color: var(--orange);
|
|
461
|
+
}
|
|
462
|
+
|
|
458
463
|
|
|
459
464
|
/***********************************************
|
|
460
465
|
* Per-component styles
|
|
@@ -601,6 +606,59 @@ a.gvs-btn { text-decoration: none; }
|
|
|
601
606
|
text-align: center;
|
|
602
607
|
font-weight: bold;
|
|
603
608
|
margin-bottom: 15px;
|
|
609
|
+
color: var(--orange);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
#gvs-filter-qualityscore {
|
|
613
|
+
gap: 0px;
|
|
614
|
+
justify-content: center;
|
|
615
|
+
height: 42px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.gvs-qualityscore, #gvs-filter-qualityscore label {
|
|
619
|
+
font-size: 18px;
|
|
620
|
+
width: 25px;
|
|
621
|
+
height: 30px;
|
|
622
|
+
line-height: 26px;
|
|
623
|
+
display: inline-block;
|
|
624
|
+
border: 1px solid white;
|
|
625
|
+
text-align: center;
|
|
626
|
+
background-color: gray;
|
|
627
|
+
color: rgba(255,255,255,0.9);
|
|
628
|
+
font-family: sans-serif;
|
|
629
|
+
font-weight: bold;
|
|
630
|
+
vertical-align: middle;
|
|
631
|
+
}
|
|
632
|
+
#gvs-filter-qualityscore label { cursor: pointer; }
|
|
633
|
+
#gvs-filter-qualityscore label:hover {
|
|
634
|
+
width: 28px;
|
|
635
|
+
height: 35px;
|
|
636
|
+
line-height: 30px;
|
|
637
|
+
border-radius: 3px;
|
|
638
|
+
font-size: 22px;
|
|
639
|
+
color: white;
|
|
640
|
+
border: 2px solid white;
|
|
641
|
+
}
|
|
642
|
+
.gvs-qualityscore:first-of-type,
|
|
643
|
+
#gvs-filter-qualityscore label:first-of-type {
|
|
644
|
+
border-top-left-radius: 8px;
|
|
645
|
+
border-bottom-left-radius: 8px;
|
|
646
|
+
}
|
|
647
|
+
.gvs-qualityscore:last-of-type,
|
|
648
|
+
#gvs-filter-qualityscore label:last-of-type {
|
|
649
|
+
border-top-right-radius: 8px;
|
|
650
|
+
border-bottom-right-radius: 8px;
|
|
651
|
+
}
|
|
652
|
+
#gvs-filter-qualityscore input[type="checkbox"] { display: none; }
|
|
653
|
+
.gvs-qualityscore-selected,
|
|
654
|
+
#gvs-filter-qualityscore input[type="checkbox"]:checked + label {
|
|
655
|
+
width: 30px;
|
|
656
|
+
height: 42px;
|
|
657
|
+
line-height: 37px;
|
|
658
|
+
border-radius: 8px;
|
|
659
|
+
font-size: 27px;
|
|
660
|
+
color: white;
|
|
661
|
+
border: 2px solid white;
|
|
604
662
|
}
|
|
605
663
|
|
|
606
664
|
|
|
@@ -650,6 +708,11 @@ a.gvs-btn { text-decoration: none; }
|
|
|
650
708
|
margin-right: 5px;
|
|
651
709
|
}
|
|
652
710
|
|
|
711
|
+
#gvs-map-theme-legend-score {
|
|
712
|
+
justify-content: center;
|
|
713
|
+
gap: 0;
|
|
714
|
+
}
|
|
715
|
+
|
|
653
716
|
|
|
654
717
|
/* Player */
|
|
655
718
|
#gvs-widget-player { justify-content: center; }
|
package/src/viewer/Widgets.js
CHANGED
|
@@ -3,9 +3,10 @@ import { PSV_ANIM_DURATION, PSV_ZOOM_DELTA, PIC_MAX_STAY_DURATION } from "../Vie
|
|
|
3
3
|
import {
|
|
4
4
|
createPanel, createGroup, fa, fat, createButton, disableButton,
|
|
5
5
|
createSearchBar, createExpandableButton, enableButton, enableCopyButton, closeOtherPanels,
|
|
6
|
-
createLinkCell, createTable, createHeader, createButtonSpan, createLabel
|
|
6
|
+
createLinkCell, createTable, createHeader, createButtonSpan, createLabel, showGrade,
|
|
7
|
+
showQualityScore,
|
|
7
8
|
} from "../utils/Widgets";
|
|
8
|
-
import { COLORS, isInIframe, getUserAccount } from "../utils/Utils";
|
|
9
|
+
import { COLORS, isInIframe, getUserAccount, QUALITYSCORE_VALUES, getGrade, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_RES_360_VALUES, QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_POND_RES, QUALITYSCORE_POND_GPS } from "../utils/Utils";
|
|
9
10
|
import SwitchBig from "../img/switch_big.svg";
|
|
10
11
|
import SwitchMini from "../img/switch_mini.svg";
|
|
11
12
|
import BackgroundAerial from "../img/bg_aerial.jpg";
|
|
@@ -50,6 +51,8 @@ import { faCircleQuestion } from "@fortawesome/free-solid-svg-icons/faCircleQues
|
|
|
50
51
|
import { faCommentDots } from "@fortawesome/free-solid-svg-icons/faCommentDots";
|
|
51
52
|
import { faAt } from "@fortawesome/free-solid-svg-icons/faAt";
|
|
52
53
|
import { faPaperPlane } from "@fortawesome/free-solid-svg-icons/faPaperPlane";
|
|
54
|
+
import { faMedal } from "@fortawesome/free-solid-svg-icons/faMedal";
|
|
55
|
+
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
|
|
53
56
|
|
|
54
57
|
|
|
55
58
|
/**
|
|
@@ -112,7 +115,8 @@ export default class Widgets {
|
|
|
112
115
|
this._initWidgetSearch();
|
|
113
116
|
this._initWidgetFilters(
|
|
114
117
|
this._viewer._api._endpoints.user_search !== null
|
|
115
|
-
&& this._viewer._api._endpoints.user_tiles !== null
|
|
118
|
+
&& this._viewer._api._endpoints.user_tiles !== null,
|
|
119
|
+
this._viewer.map && this._viewer.map._hasQualityScore()
|
|
116
120
|
);
|
|
117
121
|
this._initWidgetMapLayers();
|
|
118
122
|
this._listenMapFiltersChanges();
|
|
@@ -494,18 +498,30 @@ export default class Widgets {
|
|
|
494
498
|
|
|
495
499
|
// Camera details
|
|
496
500
|
popupContent.push(createHeader("h4", `${fat(faCamera)} ${this._t.gvs.metadata_camera}`));
|
|
497
|
-
const focal = picMeta?.properties?.["pers:interior_orientation"]?.focal_length ? `${picMeta?.properties?.["pers:interior_orientation"]?.focal_length} mm` : "
|
|
501
|
+
const focal = picMeta?.properties?.["pers:interior_orientation"]?.focal_length ? `${picMeta?.properties?.["pers:interior_orientation"]?.focal_length} mm` : "❓";
|
|
502
|
+
let resmp = picMeta?.properties?.["pers:interior_orientation"]?.["sensor_array_dimensions"];
|
|
503
|
+
if(resmp) {
|
|
504
|
+
resmp = `${resmp[0]} x ${resmp[1]} px (${Math.floor(resmp[0] * resmp[1] / 1000000)} Mpx)`;
|
|
505
|
+
}
|
|
506
|
+
let pictype = this._t.gvs.picture_flat;
|
|
507
|
+
let picFov = picMeta?.properties?.["pers:interior_orientation"]?.["field_of_view"]; // Use raw value instead of horizontalFov to avoid default showing up
|
|
508
|
+
if(picFov !== null && picFov !== undefined) {
|
|
509
|
+
if(picFov === 360) { pictype = this._t.gvs.picture_360; }
|
|
510
|
+
else { pictype += ` (${picFov}°)`; }
|
|
511
|
+
}
|
|
512
|
+
|
|
498
513
|
const cameraData = [
|
|
499
|
-
{ section: this._t.gvs.metadata_camera_make, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_manufacturer },
|
|
500
|
-
{ section: this._t.gvs.metadata_camera_model, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_model },
|
|
501
|
-
{ section: this._t.gvs.metadata_camera_type, value:
|
|
514
|
+
{ section: this._t.gvs.metadata_camera_make, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_manufacturer || "❓" },
|
|
515
|
+
{ section: this._t.gvs.metadata_camera_model, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_model || "❓" },
|
|
516
|
+
{ section: this._t.gvs.metadata_camera_type, value: pictype },
|
|
517
|
+
{ section: this._t.gvs.metadata_camera_resolution, value: resmp || "❓" },
|
|
502
518
|
{ section: this._t.gvs.metadata_camera_focal_length, value: focal },
|
|
503
519
|
];
|
|
504
520
|
popupContent.push(createTable("gvs-table-light", cameraData));
|
|
505
521
|
|
|
506
522
|
// Location details
|
|
507
523
|
popupContent.push(createHeader("h4", `${fat(faLocationDot)} ${this._t.gvs.metadata_location}`));
|
|
508
|
-
const orientation = picMeta?.properties?.["view:azimuth"] !== undefined ? `${picMeta.properties["view:azimuth"]}°` : "
|
|
524
|
+
const orientation = picMeta?.properties?.["view:azimuth"] !== undefined ? `${picMeta.properties["view:azimuth"]}°` : "❓";
|
|
509
525
|
const gpsPrecisionLabel = getGPSPrecision(picMeta);
|
|
510
526
|
const locationData = [
|
|
511
527
|
{ section: this._t.gvs.metadata_location_longitude, value: picMeta.gps[0] },
|
|
@@ -515,6 +531,28 @@ export default class Widgets {
|
|
|
515
531
|
];
|
|
516
532
|
popupContent.push(createTable("gvs-table-light", locationData));
|
|
517
533
|
|
|
534
|
+
// Picture quality level
|
|
535
|
+
if(this._viewer?.map?._hasQualityScore()) {
|
|
536
|
+
popupContent.push(createHeader(
|
|
537
|
+
"h4",
|
|
538
|
+
`${fat(faMedal)} ${this._t.gvs.metadata_quality} <a href="https://docs.panoramax.fr/pictures-metadata/quality_score/" target="_blank" title="${this._t.gvs.metadata_quality_help}">${fat(faInfoCircle)}</a>`
|
|
539
|
+
));
|
|
540
|
+
const gpsGrade = getGrade(QUALITYSCORE_GPS_VALUES, picMeta?.properties?.["quality:horizontal_accuracy"]);
|
|
541
|
+
const resGrade = getGrade(
|
|
542
|
+
picMeta?.horizontalFov === 360 ? QUALITYSCORE_RES_360_VALUES : QUALITYSCORE_RES_FLAT_VALUES,
|
|
543
|
+
picMeta?.properties?.["panoramax:horizontal_pixel_density"]
|
|
544
|
+
);
|
|
545
|
+
// Note: score is also calculated in utils/map code
|
|
546
|
+
const generalGrade = Math.round((resGrade || 1) * QUALITYSCORE_POND_RES + (gpsGrade || 1) * QUALITYSCORE_POND_GPS);
|
|
547
|
+
|
|
548
|
+
const qualityData = [
|
|
549
|
+
{ section: this._t.gvs.metadata_quality_score, value: showQualityScore(generalGrade) },
|
|
550
|
+
{ section: this._t.gvs.metadata_quality_gps_score, value: showGrade(gpsGrade, this._t) },
|
|
551
|
+
{ section: this._t.gvs.metadata_quality_resolution_score, value: showGrade(resGrade, this._t) },
|
|
552
|
+
];
|
|
553
|
+
popupContent.push(createTable("gvs-table-light", qualityData));
|
|
554
|
+
}
|
|
555
|
+
|
|
518
556
|
// EXIF
|
|
519
557
|
if (picMeta.properties?.exif) {
|
|
520
558
|
const exifDetails = document.createElement("details");
|
|
@@ -829,6 +867,7 @@ export default class Widgets {
|
|
|
829
867
|
<option value="default">${this._t.gvs.map_theme_default}</option>
|
|
830
868
|
<option value="age">${this._t.gvs.map_theme_age}</option>
|
|
831
869
|
<option value="type">${this._t.gvs.map_theme_type}</option>
|
|
870
|
+
${this._viewer?.map?._hasQualityScore() ? "<option value=\"score\">"+this._t.gvs.map_theme_score+"</option>" : ""}
|
|
832
871
|
</select>
|
|
833
872
|
</div>
|
|
834
873
|
<div>
|
|
@@ -864,6 +903,9 @@ export default class Widgets {
|
|
|
864
903
|
${this._t.gvs.picture_flat}
|
|
865
904
|
</div>
|
|
866
905
|
</div>
|
|
906
|
+
<div id="gvs-map-theme-legend-score" class="gvs-map-theme-legend gvs-hidden">
|
|
907
|
+
${QUALITYSCORE_VALUES.map(pv => "<span class=\"gvs-qualityscore\" style=\"background-color: "+pv.color+";\">"+pv.label+"</span>").join("")}
|
|
908
|
+
</div>
|
|
867
909
|
</div>`;
|
|
868
910
|
|
|
869
911
|
// Map theme events
|
|
@@ -929,7 +971,7 @@ export default class Widgets {
|
|
|
929
971
|
* This should be called only if map is enabled.
|
|
930
972
|
* @private
|
|
931
973
|
*/
|
|
932
|
-
_initWidgetFilters(hasUserSearch) {
|
|
974
|
+
_initWidgetFilters(hasUserSearch, hasQualityScore) {
|
|
933
975
|
const btnFilter = createExpandableButton("gvs-filter", faSliders, this._t.gvs.filters, this);
|
|
934
976
|
const pnlFilter = createPanel(this, btnFilter, []);
|
|
935
977
|
pnlFilter.innerHTML = `
|
|
@@ -948,10 +990,9 @@ export default class Widgets {
|
|
|
948
990
|
<input type="checkbox" id="gvs-filter-type-360" name="360" checked />
|
|
949
991
|
<label for="gvs-filter-type-360">${this._t.gvs.picture_360}</label>
|
|
950
992
|
</div>
|
|
951
|
-
<!--h4>${fat(faCamera)} ${this._t.gvs.filter_camera_model}</h4>
|
|
952
|
-
<div class="gvs-input-group" id="gvs-filter-model"></div-->
|
|
953
993
|
</form>
|
|
954
994
|
`;
|
|
995
|
+
const form = pnlFilter.children[0];
|
|
955
996
|
createGroup(
|
|
956
997
|
"gvs-widget-filter",
|
|
957
998
|
this._viewer.isWidthSmall() ? "main-top-right" : "main-top-left",
|
|
@@ -964,10 +1005,39 @@ export default class Widgets {
|
|
|
964
1005
|
pnlFilter.style.width = `${this._viewer.container.offsetWidth - 70}px`;
|
|
965
1006
|
}
|
|
966
1007
|
|
|
1008
|
+
// Create qualityscore filter
|
|
1009
|
+
if(hasQualityScore) {
|
|
1010
|
+
const title = document.createElement("h4");
|
|
1011
|
+
title.innerHTML = `${fat(faMedal)} ${this._t.gvs.filter_qualityscore}`;
|
|
1012
|
+
title.style.marginBottom = "3px";
|
|
1013
|
+
form.appendChild(title);
|
|
1014
|
+
|
|
1015
|
+
const div = document.createElement("div");
|
|
1016
|
+
div.id = "gvs-filter-qualityscore";
|
|
1017
|
+
div.classList.add("gvs-input-group");
|
|
1018
|
+
|
|
1019
|
+
QUALITYSCORE_VALUES.forEach(pv => {
|
|
1020
|
+
const input = document.createElement("input");
|
|
1021
|
+
input.id = "gvs-filter-qualityscore-" + pv.label;
|
|
1022
|
+
input.type = "checkbox";
|
|
1023
|
+
input.name = "qualityscore";
|
|
1024
|
+
input.value = pv.label;
|
|
1025
|
+
|
|
1026
|
+
const label = document.createElement("label");
|
|
1027
|
+
label.setAttribute("for", input.id);
|
|
1028
|
+
label.title = this._t.gvs.filter_qualityscore_help;
|
|
1029
|
+
label.appendChild(document.createTextNode(pv.label));
|
|
1030
|
+
label.style.backgroundColor = pv.color;
|
|
1031
|
+
|
|
1032
|
+
div.appendChild(input);
|
|
1033
|
+
div.appendChild(label);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
form.appendChild(div);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
967
1039
|
// Create search bar for users
|
|
968
1040
|
if(hasUserSearch) {
|
|
969
|
-
const form = pnlFilter.querySelector("#gvs-filter-form");
|
|
970
|
-
|
|
971
1041
|
const title = document.createElement("h4");
|
|
972
1042
|
title.innerHTML = `${fat(faUser)} ${this._t.gvs.filter_user}`;
|
|
973
1043
|
form.appendChild(title);
|
|
@@ -994,18 +1064,6 @@ export default class Widgets {
|
|
|
994
1064
|
form.appendChild(input);
|
|
995
1065
|
}
|
|
996
1066
|
|
|
997
|
-
// Create search bar for camera model
|
|
998
|
-
// TODO : implement when API is ready
|
|
999
|
-
// const cameraSearch = createSearchBar(
|
|
1000
|
-
// "gvs-filter-camera-model",
|
|
1001
|
-
// this._t.gvs.search,
|
|
1002
|
-
// () => Promise.reject(),
|
|
1003
|
-
// () => {},
|
|
1004
|
-
// this
|
|
1005
|
-
// );
|
|
1006
|
-
// document.getElementById("gvs-filter-model").appendChild(cameraSearch);
|
|
1007
|
-
|
|
1008
|
-
const form = pnlFilter.children[0];
|
|
1009
1067
|
this._formDelay = null;
|
|
1010
1068
|
|
|
1011
1069
|
const onFormChange = () => {
|
|
@@ -1041,19 +1099,33 @@ export default class Widgets {
|
|
|
1041
1099
|
const fMaxDate = document.getElementById("gvs-filter-date-end");
|
|
1042
1100
|
const fTypeFlat = document.getElementById("gvs-filter-type-flat");
|
|
1043
1101
|
const fType360 = document.getElementById("gvs-filter-type-360");
|
|
1044
|
-
// const fCamera = document.getElementById("gvs-filter-camera");
|
|
1045
1102
|
const fMapTheme = document.getElementById("gvs-map-theme");
|
|
1046
1103
|
|
|
1047
1104
|
let type = "";
|
|
1048
1105
|
if(fType360.checked && !fTypeFlat.checked) { type = "equirectangular"; }
|
|
1049
1106
|
if(!fType360.checked && fTypeFlat.checked) { type = "flat"; }
|
|
1050
1107
|
|
|
1108
|
+
let qualityscore = [];
|
|
1109
|
+
if(this._viewer?.map?._hasQualityScore()) {
|
|
1110
|
+
const fScoreA = document.getElementById("gvs-filter-qualityscore-A");
|
|
1111
|
+
const fScoreB = document.getElementById("gvs-filter-qualityscore-B");
|
|
1112
|
+
const fScoreC = document.getElementById("gvs-filter-qualityscore-C");
|
|
1113
|
+
const fScoreD = document.getElementById("gvs-filter-qualityscore-D");
|
|
1114
|
+
const fScoreE = document.getElementById("gvs-filter-qualityscore-E");
|
|
1115
|
+
if(fScoreA.checked) { qualityscore.push(5); }
|
|
1116
|
+
if(fScoreB.checked) { qualityscore.push(4); }
|
|
1117
|
+
if(fScoreC.checked) { qualityscore.push(3); }
|
|
1118
|
+
if(fScoreD.checked) { qualityscore.push(2); }
|
|
1119
|
+
if(fScoreE.checked) { qualityscore.push(1); }
|
|
1120
|
+
if(qualityscore.length == 5) { qualityscore = []; }
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1051
1123
|
const values = {
|
|
1052
1124
|
minDate: fMinDate.value,
|
|
1053
1125
|
maxDate: fMaxDate.value,
|
|
1054
1126
|
type,
|
|
1055
|
-
// camera: fCamera.value,
|
|
1056
1127
|
theme: fMapTheme.value,
|
|
1128
|
+
qualityscore,
|
|
1057
1129
|
};
|
|
1058
1130
|
|
|
1059
1131
|
this._viewer.setFilters(values);
|
|
@@ -1068,19 +1140,29 @@ export default class Widgets {
|
|
|
1068
1140
|
const fMaxDate = document.getElementById("gvs-filter-date-end");
|
|
1069
1141
|
const fTypeFlat = document.getElementById("gvs-filter-type-flat");
|
|
1070
1142
|
const fType360 = document.getElementById("gvs-filter-type-360");
|
|
1071
|
-
// const fCamera = document.getElementById("gvs-filter-camera");
|
|
1072
1143
|
const fMapTheme = document.getElementById("gvs-map-theme");
|
|
1144
|
+
const fScoreA = document.getElementById("gvs-filter-qualityscore-A");
|
|
1145
|
+
const fScoreB = document.getElementById("gvs-filter-qualityscore-B");
|
|
1146
|
+
const fScoreC = document.getElementById("gvs-filter-qualityscore-C");
|
|
1147
|
+
const fScoreD = document.getElementById("gvs-filter-qualityscore-D");
|
|
1148
|
+
const fScoreE = document.getElementById("gvs-filter-qualityscore-E");
|
|
1073
1149
|
|
|
1074
1150
|
// Update widget based on programmatic filter changes
|
|
1075
1151
|
this._viewer.addEventListener("filters-changed", e => {
|
|
1076
1152
|
if(e.detail.minDate) { fMinDate.value = e.detail.minDate; }
|
|
1077
1153
|
if(e.detail.maxDate) { fMaxDate.value = e.detail.maxDate; }
|
|
1078
|
-
// if(e.detail.camera) { fCamera.value = e.detail.camera; }
|
|
1079
1154
|
if(e.detail.theme) { fMapTheme.value = e.detail.theme; }
|
|
1080
1155
|
if(e.detail.type) {
|
|
1081
1156
|
fType360.checked = ["", "equirectangular"].includes(e.detail.type);
|
|
1082
1157
|
fTypeFlat.checked = ["", "flat"].includes(e.detail.type);
|
|
1083
1158
|
}
|
|
1159
|
+
if(e.detail.qualityscore) {
|
|
1160
|
+
fScoreA.checked = e.detail.qualityscore.includes(5) && e.detail.qualityscore.length < 5;
|
|
1161
|
+
fScoreB.checked = e.detail.qualityscore.includes(4) && e.detail.qualityscore.length < 5;
|
|
1162
|
+
fScoreC.checked = e.detail.qualityscore.includes(3) && e.detail.qualityscore.length < 5;
|
|
1163
|
+
fScoreD.checked = e.detail.qualityscore.includes(2) && e.detail.qualityscore.length < 5;
|
|
1164
|
+
fScoreE.checked = e.detail.qualityscore.includes(1) && e.detail.qualityscore.length < 5;
|
|
1165
|
+
}
|
|
1084
1166
|
this._onMapThemeChange();
|
|
1085
1167
|
});
|
|
1086
1168
|
|
package/tests/utils/Exif.test.js
CHANGED
|
@@ -19,22 +19,22 @@ describe("getExifFloat", () => {
|
|
|
19
19
|
|
|
20
20
|
describe("getGPSPrecision", () => {
|
|
21
21
|
it.each([
|
|
22
|
-
[undefined, "
|
|
23
|
-
[0.4, "
|
|
24
|
-
[0.9, "
|
|
25
|
-
[2.9, "
|
|
26
|
-
[6.9, "
|
|
27
|
-
[9.9, "
|
|
28
|
-
[20, "
|
|
29
|
-
["9/10", "
|
|
30
|
-
["99/10", "
|
|
22
|
+
[undefined, "❓"],
|
|
23
|
+
[0.4, "0.4 m"],
|
|
24
|
+
[0.9, "0.9 m"],
|
|
25
|
+
[2.9, "2.9 m"],
|
|
26
|
+
[6.9, "6.9 m"],
|
|
27
|
+
[9.9, "9.9 m"],
|
|
28
|
+
[20, "20 m"],
|
|
29
|
+
["9/10", "0.9 m"],
|
|
30
|
+
["99/10", "9.9 m"],
|
|
31
31
|
])("handles GPSHPos %s > %s", (input, expected) => {
|
|
32
32
|
const p = { properties: { exif: { "Exif.GPSInfo.GPSHPositioningError": input, "Exif.GPSInfo.GPSDOP": input } } };
|
|
33
33
|
expect(Exif.getGPSPrecision(p)).toBe(expected);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it.each([
|
|
37
|
-
[undefined, "
|
|
37
|
+
[undefined, "❓"],
|
|
38
38
|
[0.9, "ideal"],
|
|
39
39
|
[1.9, "excellent"],
|
|
40
40
|
[4.9, "good"],
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
exports[`_loadMapStyles handles default user 1`] = `
|
|
4
4
|
Object {
|
|
5
5
|
"layers": Array [],
|
|
6
|
+
"metadata": Object {},
|
|
6
7
|
"sources": Object {
|
|
7
8
|
"geovisio": Object {
|
|
8
9
|
"maxzoom": 15,
|
|
@@ -30,6 +31,7 @@ Object {
|
|
|
30
31
|
"id": "provider_blo",
|
|
31
32
|
},
|
|
32
33
|
],
|
|
34
|
+
"metadata": Object {},
|
|
33
35
|
"sources": Object {
|
|
34
36
|
"provider": Object {},
|
|
35
37
|
"provider_bla": Object {},
|
|
@@ -46,6 +48,7 @@ Object {
|
|
|
46
48
|
"id": "provlayer",
|
|
47
49
|
},
|
|
48
50
|
],
|
|
51
|
+
"metadata": Object {},
|
|
49
52
|
"name": "Provider",
|
|
50
53
|
"sources": Object {
|
|
51
54
|
"geovisio": Object {
|
|
@@ -69,6 +72,7 @@ Object {
|
|
|
69
72
|
"id": "provlayer",
|
|
70
73
|
},
|
|
71
74
|
],
|
|
75
|
+
"metadata": Object {},
|
|
72
76
|
"name": "Provider",
|
|
73
77
|
"sources": Object {
|
|
74
78
|
"geovisio": Object {
|
|
@@ -88,6 +92,7 @@ Object {
|
|
|
88
92
|
exports[`_loadMapStyles works if no background style set 1`] = `
|
|
89
93
|
Object {
|
|
90
94
|
"layers": Array [],
|
|
95
|
+
"metadata": Object {},
|
|
91
96
|
"sources": Object {
|
|
92
97
|
"geovisio": Object {
|
|
93
98
|
"maxzoom": 15,
|