@mapcreator/sdk 0.0.7 → 0.0.9
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/dist/{HighlightManager.d.ts → esm/HighlightManager.d.ts} +2 -2
- package/dist/esm/HighlightManager.js +203 -0
- package/dist/{MCMap.d.ts → esm/MCMap.d.ts} +3 -5
- package/dist/esm/MCMap.js +254 -0
- package/dist/{PopupManager.d.ts → esm/PopupManager.d.ts} +4 -4
- package/dist/esm/PopupManager.js +297 -0
- package/dist/{Registry.d.ts → esm/Registry.d.ts} +3 -3
- package/dist/esm/Registry.js +74 -0
- package/dist/{adornments → esm/adornments}/categoricalLegend.d.ts +1 -1
- package/dist/esm/adornments/categoricalLegend.js +141 -0
- package/dist/{adornments → esm/adornments}/connectedLegend.d.ts +2 -2
- package/dist/esm/adornments/connectedLegend.js +393 -0
- package/dist/{adornments → esm/adornments}/customAdornment.d.ts +1 -1
- package/dist/esm/adornments/customAdornment.js +29 -0
- package/dist/{adornments → esm/adornments}/heading.d.ts +1 -1
- package/dist/esm/adornments/heading.js +71 -0
- package/dist/esm/adornments/insetMap.d.ts +3 -0
- package/dist/esm/adornments/insetMap.js +351 -0
- package/dist/{adornments → esm/adornments}/manualLegend.d.ts +1 -1
- package/dist/esm/adornments/manualLegend.js +15 -0
- package/dist/esm/adornments/northArrow.d.ts +3 -0
- package/dist/esm/adornments/northArrow.js +24 -0
- package/dist/esm/adornments/scalebar.d.ts +3 -0
- package/dist/esm/adornments/scalebar.js +176 -0
- package/dist/{constants → esm/constants}/index.d.ts +2 -2
- package/dist/esm/constants/index.js +53 -0
- package/dist/esm/controls/controls.js +7 -0
- package/dist/{controls → esm/controls}/fullscreenControls.d.ts +1 -1
- package/dist/esm/controls/fullscreenControls.js +29 -0
- package/dist/{controls → esm/controls}/geocoderControl.d.ts +1 -1
- package/dist/esm/controls/geocoderControl.js +202 -0
- package/dist/{controls → esm/controls}/geolocationControls.d.ts +1 -1
- package/dist/esm/controls/geolocationControls.js +65 -0
- package/dist/esm/controls/refreshMapControls.d.ts +3 -0
- package/dist/esm/controls/refreshMapControls.js +26 -0
- package/dist/esm/controls/webControls.d.ts +4 -0
- package/dist/esm/controls/webControls.js +40 -0
- package/dist/{controls → esm/controls}/zoomControls.d.ts +1 -1
- package/dist/esm/controls/zoomControls.js +23 -0
- package/dist/esm/i18n.js +21 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/locales/da_DK/strings.json +7 -0
- package/dist/esm/locales/de_DE/strings.json +7 -0
- package/dist/esm/locales/en_GB/strings.json +7 -0
- package/dist/esm/locales/es_ES/strings.json +7 -0
- package/dist/esm/locales/fr_FR/strings.json +7 -0
- package/dist/esm/locales/it_IT/strings.json +7 -0
- package/dist/esm/locales/nl_NL/strings.json +7 -0
- package/dist/esm/models/area.d.ts +5 -0
- package/dist/esm/models/area.js +165 -0
- package/dist/esm/models/circle.d.ts +5 -0
- package/dist/esm/models/circle.js +110 -0
- package/dist/esm/models/dot.d.ts +3 -0
- package/dist/esm/models/dot.js +42 -0
- package/dist/esm/models/line.d.ts +4 -0
- package/dist/esm/models/line.js +117 -0
- package/dist/esm/models/marker.d.ts +5 -0
- package/dist/esm/models/marker.js +179 -0
- package/dist/esm/models/polygon.d.ts +5 -0
- package/dist/esm/models/polygon.js +80 -0
- package/dist/{renderAdornments.d.ts → esm/renderAdornments.d.ts} +3 -3
- package/dist/esm/renderAdornments.js +129 -0
- package/dist/esm/types/geometry.js +1 -0
- package/dist/{types → esm/types}/index.d.ts +1 -1
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/types/jobObject.js +1 -0
- package/dist/{types → esm/types}/mapstyle.d.ts +6 -2
- package/dist/esm/types/mapstyle.js +1 -0
- package/dist/esm/utils/browser.js +6 -0
- package/dist/{utils → esm/utils}/choropleth.d.ts +3 -3
- package/dist/esm/utils/choropleth.js +110 -0
- package/dist/esm/utils/fullscreen.js +40 -0
- package/dist/{utils → esm/utils}/geolocation.d.ts +1 -1
- package/dist/esm/utils/geolocation.js +93 -0
- package/dist/{utils → esm/utils}/graphhopper.d.ts +1 -1
- package/dist/esm/utils/graphhopper.js +41 -0
- package/dist/{utils → esm/utils}/helpers.d.ts +2 -2
- package/dist/esm/utils/helpers.js +116 -0
- package/dist/{utils → esm/utils}/language.d.ts +1 -1
- package/dist/esm/utils/language.js +170 -0
- package/dist/{utils → esm/utils}/models.d.ts +4 -4
- package/dist/esm/utils/models.js +103 -0
- package/dist/{utils → esm/utils}/overlays.d.ts +1 -1
- package/dist/esm/utils/overlays.js +87 -0
- package/dist/esm/utils/scalebar.js +52 -0
- package/dist/{utils → esm/utils}/svgHelpers.d.ts +4 -3
- package/dist/esm/utils/svgHelpers.js +1512 -0
- package/dist/{utils → esm/utils}/template.d.ts +2 -2
- package/dist/esm/utils/template.js +120 -0
- package/dist/{utils → esm/utils}/youtube.d.ts +1 -1
- package/dist/esm/utils/youtube.js +64 -0
- package/dist/favicon-32x32.png +0 -0
- package/dist/mapcreator-sdk.umd.cjs +91 -91
- package/dist/report.html +4950 -0
- package/package.json +7 -7
- package/dist/adornments/insetMap.d.ts +0 -3
- package/dist/adornments/northArrow.d.ts +0 -3
- package/dist/adornments/scalebar.d.ts +0 -3
- package/dist/controls/refreshMapControls.d.ts +0 -3
- package/dist/controls/webControls.d.ts +0 -4
- package/dist/index.d.ts +0 -2
- package/dist/locales/da_DK/strings.json.d.ts +0 -10
- package/dist/locales/de_DE/strings.json.d.ts +0 -10
- package/dist/locales/en_GB/strings.json.d.ts +0 -10
- package/dist/locales/es_ES/strings.json.d.ts +0 -10
- package/dist/locales/fr_FR/strings.json.d.ts +0 -10
- package/dist/locales/it_IT/strings.json.d.ts +0 -10
- package/dist/locales/nl_NL/strings.json.d.ts +0 -10
- package/dist/mapcreator-sdk.js +0 -39590
- package/dist/models/area.d.ts +0 -5
- package/dist/models/circle.d.ts +0 -5
- package/dist/models/dot.d.ts +0 -3
- package/dist/models/line.d.ts +0 -4
- package/dist/models/marker.d.ts +0 -5
- package/dist/models/polygon.d.ts +0 -5
- /package/dist/{controls → esm/controls}/controls.d.ts +0 -0
- /package/dist/{i18n.d.ts → esm/i18n.d.ts} +0 -0
- /package/dist/{types → esm/types}/geometry.d.ts +0 -0
- /package/dist/{types → esm/types}/jobObject.d.ts +0 -0
- /package/dist/{utils → esm/utils}/browser.d.ts +0 -0
- /package/dist/{utils → esm/utils}/fullscreen.d.ts +0 -0
- /package/dist/{utils → esm/utils}/scalebar.d.ts +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useCustomAdornment } from '@/adornments/customAdornment';
|
|
2
|
+
import { useConnectedLegend } from '@/adornments/connectedLegend';
|
|
3
|
+
import { useManualLegend } from '@/adornments/manualLegend';
|
|
4
|
+
import { useNorthArrow } from '@/adornments/northArrow';
|
|
5
|
+
import { useWebControls } from '@/controls/webControls';
|
|
6
|
+
import { useInsetMap } from '@/adornments/insetMap';
|
|
7
|
+
import { useScalebar } from '@/adornments/scalebar';
|
|
8
|
+
import { useHeading } from '@/adornments/heading';
|
|
9
|
+
import { positionConfig } from '@/constants';
|
|
10
|
+
const positions = [
|
|
11
|
+
'top_left',
|
|
12
|
+
'top_right',
|
|
13
|
+
'bottom_left',
|
|
14
|
+
'bottom_right',
|
|
15
|
+
'top_center',
|
|
16
|
+
'left_center',
|
|
17
|
+
'right_center',
|
|
18
|
+
'bottom_center',
|
|
19
|
+
];
|
|
20
|
+
const adornmentContainers = new Map();
|
|
21
|
+
export function useRenderAdornments(jobObject, positionOffsets, map, initialPosition, vapiUrl, cdnUrl, accessToken) {
|
|
22
|
+
const { adornments = [] } = jobObject;
|
|
23
|
+
createContainers(map, positionOffsets);
|
|
24
|
+
adornments.forEach(adornment => {
|
|
25
|
+
if (adornment.type === 'custom') {
|
|
26
|
+
const customElement = useCustomAdornment(adornment);
|
|
27
|
+
addAdornment(customElement, adornment.position, adornment.stacking);
|
|
28
|
+
}
|
|
29
|
+
else if (adornment.type === 'scalebar') {
|
|
30
|
+
const scalebarElement = useScalebar(adornment, map);
|
|
31
|
+
addAdornment(scalebarElement, adornment.position, adornment.stacking);
|
|
32
|
+
}
|
|
33
|
+
else if (adornment.type === 'northArrow') {
|
|
34
|
+
const northArrowElement = useNorthArrow(adornment, map);
|
|
35
|
+
addAdornment(northArrowElement, adornment.position, adornment.stacking);
|
|
36
|
+
}
|
|
37
|
+
else if (adornment.type === 'webControls' && adornment.buttons) {
|
|
38
|
+
const webControlsElement = useWebControls(adornment, map, initialPosition);
|
|
39
|
+
addAdornment(webControlsElement, adornment.position, adornment.stacking);
|
|
40
|
+
}
|
|
41
|
+
else if (adornment.type === 'insetMap') {
|
|
42
|
+
const insetMapElement = useInsetMap(adornment, map, cdnUrl);
|
|
43
|
+
addAdornment(insetMapElement, adornment.position, adornment.stacking);
|
|
44
|
+
}
|
|
45
|
+
else if (adornment.type === 'heading') {
|
|
46
|
+
const headerElement = useHeading(adornment, vapiUrl, accessToken);
|
|
47
|
+
addAdornment(headerElement, adornment.position, adornment.stacking);
|
|
48
|
+
}
|
|
49
|
+
else if (adornment.type === 'manualLegend') {
|
|
50
|
+
const categoricalLegendElement = useManualLegend(adornment, vapiUrl, accessToken);
|
|
51
|
+
addAdornment(categoricalLegendElement, adornment.position, adornment.stacking);
|
|
52
|
+
}
|
|
53
|
+
else if (adornment.type === 'connectedLegend') {
|
|
54
|
+
const connectedLegendElement = useConnectedLegend(adornment, jobObject, map, vapiUrl, accessToken);
|
|
55
|
+
addAdornment(connectedLegendElement, adornment.position, adornment.stacking);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
updateCollisionDetection(map);
|
|
59
|
+
map.on('moveend', () => updateCollisionDetection(map));
|
|
60
|
+
}
|
|
61
|
+
function createContainers(map, positionOffsets) {
|
|
62
|
+
const mapContainer = map.getContainer();
|
|
63
|
+
for (const position of positions) {
|
|
64
|
+
const container = document.createElement('div');
|
|
65
|
+
container.classList.add('adornment-container', `${position}`);
|
|
66
|
+
if (positionOffsets?.[position]) {
|
|
67
|
+
container.style.cssText = getContainerStyle(position, positionOffsets);
|
|
68
|
+
}
|
|
69
|
+
const horizontal = document.createElement('div');
|
|
70
|
+
const vertical = document.createElement('div');
|
|
71
|
+
container.classList.add('adornment-container', position);
|
|
72
|
+
horizontal.classList.add('inner');
|
|
73
|
+
vertical.classList.add('inner', 'vertical');
|
|
74
|
+
container.append(horizontal, vertical);
|
|
75
|
+
mapContainer.appendChild(container);
|
|
76
|
+
adornmentContainers.set(position, {
|
|
77
|
+
horizontal,
|
|
78
|
+
vertical,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function getContainerStyle(position, positionOffsets) {
|
|
83
|
+
const cfg = positionConfig[position];
|
|
84
|
+
const { x, y, unit } = positionOffsets[position];
|
|
85
|
+
let style = '';
|
|
86
|
+
const transforms = [];
|
|
87
|
+
if (cfg.x === 'center') {
|
|
88
|
+
transforms.push(`translateX(${x}${unit})`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
style += `${cfg.x}: ${x}${unit};`;
|
|
92
|
+
}
|
|
93
|
+
if (cfg.y === 'center') {
|
|
94
|
+
transforms.push(`translateY(${y}${unit})`);
|
|
95
|
+
}
|
|
96
|
+
else if (position === 'bottom_right') {
|
|
97
|
+
style += `${cfg.y}: max(${y}${unit}, 25px);`;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
style += `${cfg.y}: ${y}${unit};`;
|
|
101
|
+
}
|
|
102
|
+
if (transforms.length) {
|
|
103
|
+
style += `transform: ${transforms.join(' ')};`;
|
|
104
|
+
}
|
|
105
|
+
return style;
|
|
106
|
+
}
|
|
107
|
+
function addAdornment(adornment, position, stacking) {
|
|
108
|
+
if (!stacking) {
|
|
109
|
+
stacking = ['left_center', 'right_center'].includes(position) ? 'vertical' : 'horizontal';
|
|
110
|
+
}
|
|
111
|
+
const container = adornmentContainers.get(position);
|
|
112
|
+
const dominantAxis = position === 'left_center' || position === 'right_center' ? 'vertical' : 'horizontal';
|
|
113
|
+
if (!container?.[dominantAxis].hasChildNodes()) {
|
|
114
|
+
stacking = dominantAxis;
|
|
115
|
+
}
|
|
116
|
+
const target = container?.[stacking];
|
|
117
|
+
target?.appendChild(adornment);
|
|
118
|
+
}
|
|
119
|
+
function updateCollisionDetection(map) {
|
|
120
|
+
const elements = Array.from(document.querySelectorAll('.adornment, .maplibregl-ctrl-attrib'));
|
|
121
|
+
const labelFreeAreas = [];
|
|
122
|
+
elements.forEach(el => {
|
|
123
|
+
const rect = el.getBoundingClientRect();
|
|
124
|
+
if (rect) {
|
|
125
|
+
labelFreeAreas.push([rect.x, rect.y, rect.x + rect.width, rect.y + rect.height]);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
map.setLabelFreeAreas(labelFreeAreas);
|
|
129
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* These types are mostly copy-pasted from V5 and needs to stay in sync with it.
|
|
3
|
+
* TODO: consider extracting into a separate package
|
|
4
|
+
*/
|
|
5
|
+
import type { JobObjectModelType } from '@/types/jobObject';
|
|
6
|
+
import type { LayerSpecification } from '@mapcreator/maplibre-gl';
|
|
3
7
|
export type Anchor = 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
4
8
|
export type Justify = 'left' | 'center' | 'right';
|
|
5
9
|
export interface LabelStylePreset {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ScaleLinear, ScaleOrdinal, ScaleQuantile, ScaleQuantize } from 'd3-scale';
|
|
2
|
-
import { CategoricalGroup, JobObjectAreaGroup } from '
|
|
3
|
-
import { ModelBindings } from '
|
|
1
|
+
import { type ScaleLinear, type ScaleOrdinal, type ScaleQuantile, type ScaleQuantize } from 'd3-scale';
|
|
2
|
+
import type { CategoricalGroup, JobObjectAreaGroup } from '@/types/jobObject';
|
|
3
|
+
import type { ModelBindings } from '@/types';
|
|
4
4
|
export type ColorGenerator = ScaleLinear<string, string> | ScaleQuantile<string> | ScaleQuantize<string> | ScaleOrdinal<string, string>;
|
|
5
5
|
export declare const legendTickCount = 4;
|
|
6
6
|
export declare function getColorGenerator(group: JobObjectAreaGroup): ColorGenerator | undefined;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { interpolateHcl, piecewise, quantize } from 'd3-interpolate';
|
|
2
|
+
import { extent, range } from 'd3-array';
|
|
3
|
+
import { scaleLinear, scaleOrdinal, scaleQuantile, scaleQuantize, } from 'd3-scale';
|
|
4
|
+
import { getBoundValues } from '@/utils/models';
|
|
5
|
+
// constants/choropleth.ts
|
|
6
|
+
const multiHue = [
|
|
7
|
+
['#EDF8E9', '#BAE4B3', '#74C476', '#31A354', '#006D2C'],
|
|
8
|
+
['#EFF3FF', '#BDD7E7', '#6BAED6', '#3182BD', '#08519C'],
|
|
9
|
+
['#F0F9E8', '#BAE4BC', '#7BCCC4', '#43A2CA', '#0868AC'],
|
|
10
|
+
['#EDF8FB', '#B3CDE3', '#8C96C6', '#8856A7', '#810F7C'],
|
|
11
|
+
['#FEF0D9', '#FDCC8A', '#FC8D59', '#E34A33', '#B30000'],
|
|
12
|
+
['#FEEBE2', '#FBB4B9', '#F768A1', '#C51B8A', '#7A0177'],
|
|
13
|
+
['#D7191C', '#FDAE61', '#FFFFBF', '#ABDDA4', '#2B83BA'],
|
|
14
|
+
['#7B3294', '#C2A5CF', '#F7F7F7', '#A6DBA0', '#008837'],
|
|
15
|
+
];
|
|
16
|
+
const singleHue = [
|
|
17
|
+
// ['#F7F7F7', '#CCCCCC', '#969696', '#636363', '#252525'],
|
|
18
|
+
// ['#EDF8E9', '#BAE4B3', '#74C476', '#31A354', '#006D2C'],
|
|
19
|
+
// ['#FEEDDE', '#FDBE85', '#FD8D3C', '#E6550D', '#A63603'],
|
|
20
|
+
// ['#FEE5D9', '#FCAE91', '#FB6A4A', '#DE2D26', '#A50F15'],
|
|
21
|
+
// ['#EFF3FF', '#BDD7E7', '#6BAED6', '#3182BD', '#08519C'],
|
|
22
|
+
// ['#F2F0F7', '#CBC9E2', '#9E9AC8', '#756BB1', '#54278F'],
|
|
23
|
+
];
|
|
24
|
+
// utils/import/pattern.ts
|
|
25
|
+
const colourReArr = [
|
|
26
|
+
/transparent/,
|
|
27
|
+
/#[0-9a-fA-F]{3,4}/,
|
|
28
|
+
/#[0-9a-fA-F]{6}/,
|
|
29
|
+
/#[0-9a-fA-F]{8}/,
|
|
30
|
+
/rgba?\(\s*(?:(?:\d{1,3}%?|none)\s*,\s*){2}(?:\d{1,3}%?|none)(?:\s*,\s*(?:0|1|0?\.\d+|\d+%|none))?\s*\)/,
|
|
31
|
+
/rgba?\(\s*(?:\d{1,3}%?|none)\s+(?:\d{1,3}%?|none)\s+(?:\d{1,3}%?|none)(?:\s*\/\s*(?:0|1|0?\.\d+|\d+%|none))?\s*\)/,
|
|
32
|
+
/hsla?\(\s*(?:[+-]?\d*\.?\d+(?:deg|grad|rad|turn)?|none)\s*,\s*(?:\d{1,3}%|none)\s*,\s*(?:\d{1,3}%|none)(?:\s*,\s*(?:0|1|0?\.\d+|\d+%|none))?\s*\)/,
|
|
33
|
+
/hsla?\(\s*(?:[+-]?\d*\.?\d+(?:deg|grad|rad|turn)?|none)\s+(?:\d{1,3}%|none)\s+(?:\d{1,3}%|none)(?:\s*\/\s*(?:0|1|0?\.\d+|\d+%|none))?\s*\)/,
|
|
34
|
+
// /(?:oklab|oklch|lab|lch|hwb|color)\(\s*[^()]*\)/,
|
|
35
|
+
];
|
|
36
|
+
const colourReStr = colourReArr.map(re => re.toString().replace(/^\/|\/$/g, '')).join('|');
|
|
37
|
+
const colourRe = new RegExp(`^\\s*(${colourReStr})\\s*$`);
|
|
38
|
+
function linearDomain(values, colors) {
|
|
39
|
+
const [min, max] = extent(values);
|
|
40
|
+
const n = colors.length;
|
|
41
|
+
return range(n).map(i => min + (i * (max - min)) / (n - 1));
|
|
42
|
+
}
|
|
43
|
+
export const legendTickCount = 4;
|
|
44
|
+
const colorMethods = {
|
|
45
|
+
linear: (values, colors) => scaleLinear(linearDomain(values, colors), colors)
|
|
46
|
+
.interpolate(interpolateHcl)
|
|
47
|
+
.nice(legendTickCount),
|
|
48
|
+
quantile: (values, colors) => scaleQuantile(values, colors),
|
|
49
|
+
quantize: (values, colors) => scaleQuantize(extent(values), colors).nice(),
|
|
50
|
+
categorical: (values, colors) => scaleOrdinal(values, colors),
|
|
51
|
+
};
|
|
52
|
+
export function getColorGenerator(group) {
|
|
53
|
+
if (group.choropleth) {
|
|
54
|
+
const values = getChoroplethValues(group);
|
|
55
|
+
const dataColumn = getColumnName(group.fillColor);
|
|
56
|
+
if (values && dataColumn) {
|
|
57
|
+
const { choropleth } = group;
|
|
58
|
+
const categoricalGroup = choropleth.categoricalColors[dataColumn];
|
|
59
|
+
const colors = choropleth.colorMode === 'categorical'
|
|
60
|
+
? values.map(value => categoricalGroup.values[value])
|
|
61
|
+
: getColorSamples(choropleth.lowerColor, choropleth.upperColor, choropleth.colorMode !== 'linear' ? choropleth.colorCount : undefined);
|
|
62
|
+
return colorMethods[choropleth.colorMode](values, colors);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function getChoroplethValues(group) {
|
|
67
|
+
if (group.choropleth) {
|
|
68
|
+
const dataColumn = group.fillColor?.match(/\$\{([^}]+)}/)?.[1];
|
|
69
|
+
if (dataColumn) {
|
|
70
|
+
const bindings = Object.values(group.models);
|
|
71
|
+
const values = getBoundValues(bindings, dataColumn);
|
|
72
|
+
if (!values.length ||
|
|
73
|
+
values.some(value => typeof value !== 'string' || !colourRe.test(value))) {
|
|
74
|
+
return group.choropleth.colorMode === 'categorical' ? Array.from(new Set(values)) : values;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function getColorSamples(lowerColor, upperColor, colorCount) {
|
|
80
|
+
const stops = multiHue.find(r => r[0] === lowerColor && r[r.length - 1] === upperColor) ??
|
|
81
|
+
singleHue.find(r => r[0] === lowerColor && r[r.length - 1] === upperColor);
|
|
82
|
+
return stops
|
|
83
|
+
? quantize(piecewise(interpolateHcl, stops), colorCount ?? stops.length)
|
|
84
|
+
: quantize(interpolateHcl(lowerColor, upperColor), colorCount ?? 2);
|
|
85
|
+
}
|
|
86
|
+
export function getColumnType(modelBindings, dataColumn) {
|
|
87
|
+
const values = dataColumn ? getBoundValues(modelBindings, dataColumn) : undefined;
|
|
88
|
+
return !values?.length
|
|
89
|
+
? undefined
|
|
90
|
+
: values.every(value => typeof value === 'number')
|
|
91
|
+
? 'number'
|
|
92
|
+
: values.every(value => typeof value === 'string' && colourRe.test(value))
|
|
93
|
+
? 'color'
|
|
94
|
+
: 'string';
|
|
95
|
+
}
|
|
96
|
+
export function getColumnName(value) {
|
|
97
|
+
return value?.match(/\$\{([^}]+)}/)?.[1];
|
|
98
|
+
}
|
|
99
|
+
export function groupByColor(values) {
|
|
100
|
+
const result = Object.create(null);
|
|
101
|
+
for (const key in values) {
|
|
102
|
+
const value = values[key];
|
|
103
|
+
result[value] = value in result ? null : key;
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
export function isChoroplethColumn(modelBindings, dataColumn) {
|
|
108
|
+
const columnType = dataColumn ? getColumnType(modelBindings, dataColumn) : undefined;
|
|
109
|
+
return columnType !== undefined && columnType !== 'color';
|
|
110
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function isFullscreenSupported() {
|
|
2
|
+
return Boolean(document.fullscreenEnabled ||
|
|
3
|
+
document.mozFullScreenEnabled ||
|
|
4
|
+
document.msFullscreenEnabled ||
|
|
5
|
+
document.webkitFullscreenEnabled);
|
|
6
|
+
}
|
|
7
|
+
export function isFullscreen(mapContainer) {
|
|
8
|
+
return (document.fullscreenElement === mapContainer ||
|
|
9
|
+
document.webkitFullscreenElement === mapContainer ||
|
|
10
|
+
document.mozFullScreenElement === mapContainer ||
|
|
11
|
+
document.msFullscreenElement === mapContainer);
|
|
12
|
+
}
|
|
13
|
+
export function enterFullscreen(mapContainer) {
|
|
14
|
+
if (mapContainer.requestFullscreen) {
|
|
15
|
+
mapContainer.requestFullscreen();
|
|
16
|
+
}
|
|
17
|
+
else if (mapContainer.mozRequestFullScreen) {
|
|
18
|
+
mapContainer.mozRequestFullScreen();
|
|
19
|
+
}
|
|
20
|
+
else if (mapContainer.msRequestFullscreen) {
|
|
21
|
+
mapContainer.msRequestFullscreen();
|
|
22
|
+
}
|
|
23
|
+
else if (mapContainer.webkitRequestFullscreen) {
|
|
24
|
+
mapContainer.webkitRequestFullscreen();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function exitFullscreen() {
|
|
28
|
+
if (document.exitFullscreen) {
|
|
29
|
+
document.exitFullscreen();
|
|
30
|
+
}
|
|
31
|
+
else if (document.mozCancelFullScreen) {
|
|
32
|
+
document.mozCancelFullScreen();
|
|
33
|
+
}
|
|
34
|
+
else if (document.msExitFullscreen) {
|
|
35
|
+
document.msExitFullscreen();
|
|
36
|
+
}
|
|
37
|
+
else if (document.webkitCancelFullScreen) {
|
|
38
|
+
document.webkitCancelFullScreen();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Map } from '@mapcreator/maplibre-gl';
|
|
1
|
+
import type { Map } from '@mapcreator/maplibre-gl';
|
|
2
2
|
export declare const loadLocationDot: (map: Map) => void;
|
|
3
3
|
export declare const initLayersAndSources: (map: Map) => void;
|
|
4
4
|
export declare const addLocationDot: (map: any, lng: number, lat: number) => void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import locationDot from '@/images/location-dot.svg?raw';
|
|
2
|
+
import { SvgCache } from '@/utils/svgHelpers';
|
|
3
|
+
export const loadLocationDot = (map) => {
|
|
4
|
+
const pixelRatio = 2 * window.devicePixelRatio;
|
|
5
|
+
const svgCache = new SvgCache(map);
|
|
6
|
+
const imageKey = svgCache.getMapLibreImageKey(locationDot, pixelRatio);
|
|
7
|
+
map.setLayoutProperty('location-dot', 'icon-image', imageKey);
|
|
8
|
+
};
|
|
9
|
+
export const initLayersAndSources = (map) => {
|
|
10
|
+
if (!map.getSource('location-circle')) {
|
|
11
|
+
map.addSource('location-circle', {
|
|
12
|
+
type: 'geojson',
|
|
13
|
+
data: { type: 'FeatureCollection', features: [] },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (!map.getSource('location-dot')) {
|
|
17
|
+
map.addSource('location-dot', {
|
|
18
|
+
type: 'geojson',
|
|
19
|
+
data: {
|
|
20
|
+
type: 'Feature',
|
|
21
|
+
geometry: { type: 'Point', coordinates: [] },
|
|
22
|
+
properties: {},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!map.getLayer('location-circle')) {
|
|
27
|
+
map.addLayer({
|
|
28
|
+
id: 'location-circle',
|
|
29
|
+
type: 'fill',
|
|
30
|
+
source: 'location-circle',
|
|
31
|
+
layout: {},
|
|
32
|
+
paint: {
|
|
33
|
+
'fill-color': '#349CF2',
|
|
34
|
+
'fill-opacity': 0.15,
|
|
35
|
+
'fill-outline-color': '#005196',
|
|
36
|
+
},
|
|
37
|
+
}, 'mc-before-names');
|
|
38
|
+
}
|
|
39
|
+
if (!map.getLayer('location-dot')) {
|
|
40
|
+
map.addLayer({
|
|
41
|
+
id: 'location-dot',
|
|
42
|
+
type: 'symbol',
|
|
43
|
+
source: 'location-dot',
|
|
44
|
+
layout: { 'icon-padding': 0 },
|
|
45
|
+
paint: { 'icon-opacity': 1 },
|
|
46
|
+
metadata: { 'mc-name': 'location dot', 'mc-skip-load': true },
|
|
47
|
+
}, 'mc-before-names');
|
|
48
|
+
}
|
|
49
|
+
loadLocationDot(map);
|
|
50
|
+
};
|
|
51
|
+
export const addLocationDot = (map, lng, lat) => {
|
|
52
|
+
const source = map.getSource('location-dot');
|
|
53
|
+
if (source) {
|
|
54
|
+
source.setData({
|
|
55
|
+
type: 'Feature',
|
|
56
|
+
geometry: { type: 'Point', coordinates: [lng, lat] },
|
|
57
|
+
properties: {},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
export const createCircle = (lng, lat, meters) => {
|
|
62
|
+
const points = 64;
|
|
63
|
+
const km = meters / 1000;
|
|
64
|
+
const polygon = [];
|
|
65
|
+
const deltaX = km / (111.32 * Math.cos((lat * Math.PI) / 180));
|
|
66
|
+
const deltaY = km / 110.574;
|
|
67
|
+
for (let i = 0; i < points; i++) {
|
|
68
|
+
const angle = (i / points) * (2 * Math.PI);
|
|
69
|
+
const x = deltaX * Math.cos(angle);
|
|
70
|
+
const y = deltaY * Math.sin(angle);
|
|
71
|
+
polygon.push([lng + x, lat + y]);
|
|
72
|
+
}
|
|
73
|
+
polygon.push(polygon[0]);
|
|
74
|
+
return {
|
|
75
|
+
type: 'FeatureCollection',
|
|
76
|
+
features: [
|
|
77
|
+
{
|
|
78
|
+
type: 'Feature',
|
|
79
|
+
geometry: { type: 'Polygon', coordinates: [polygon] },
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
export const showNotification = () => {
|
|
85
|
+
const mapContainer = document.querySelector('.map');
|
|
86
|
+
const notificationElem = document.createElement('div');
|
|
87
|
+
notificationElem.classList.add('location-notification');
|
|
88
|
+
notificationElem.textContent = 'Geolocation is not available';
|
|
89
|
+
mapContainer?.appendChild(notificationElem);
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
mapContainer?.removeChild(notificationElem);
|
|
92
|
+
}, 3000);
|
|
93
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LngLat } from '
|
|
1
|
+
import type { LngLat } from '@/types/geometry';
|
|
2
2
|
export declare class GraphHopperGeocoderService {
|
|
3
3
|
getLocationInBounds(location: string, center: LngLat, limit?: number): Promise<any>;
|
|
4
4
|
_callGraphHopper(params: any): Promise<any>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { locale } from '@/i18n';
|
|
2
|
+
export class GraphHopperGeocoderService {
|
|
3
|
+
async getLocationInBounds(location, center, limit = 10) {
|
|
4
|
+
try {
|
|
5
|
+
const results = await this._callGraphHopper({
|
|
6
|
+
limit,
|
|
7
|
+
q: location,
|
|
8
|
+
point: `${center.lat},${center.lng}`,
|
|
9
|
+
});
|
|
10
|
+
return this._mapResults(results.hits);
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
console.warn('Geocoding failed: ', e);
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async _callGraphHopper(params) {
|
|
18
|
+
const query = new URLSearchParams(params);
|
|
19
|
+
query.append('key', import.meta.env.VITE_GRAPHHOPPER_GEOCODING_CODE);
|
|
20
|
+
query.append('locale', locale.slice(0, 2));
|
|
21
|
+
const init = { headers: { Accept: 'application/json' } };
|
|
22
|
+
const url = `https://graphhopper.com/api/1/geocode?${query}`;
|
|
23
|
+
const response = await fetch(url, init);
|
|
24
|
+
const res = await response.json();
|
|
25
|
+
return res;
|
|
26
|
+
}
|
|
27
|
+
_mapResults(results) {
|
|
28
|
+
return results.map((r) => ({
|
|
29
|
+
id: r.osm_id.toString(),
|
|
30
|
+
labelTitle: r.name,
|
|
31
|
+
position: { lng: r.point.lng, lat: r.point.lat },
|
|
32
|
+
bbox: r.extent
|
|
33
|
+
? [
|
|
34
|
+
{ lng: r.extent[0], lat: r.extent[1] },
|
|
35
|
+
{ lng: r.extent[2], lat: r.extent[3] },
|
|
36
|
+
]
|
|
37
|
+
: null,
|
|
38
|
+
subtitle: [r.osm_value, r.city, r.state, r.country].filter(Boolean).join(', '),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { JobObjectDataBindings, JobObjectStoredValue } from '
|
|
2
|
-
import { LngLat } from '
|
|
1
|
+
import type { JobObjectDataBindings, JobObjectStoredValue } from '@/types/jobObject';
|
|
2
|
+
import type { LngLat } from '@/types/geometry';
|
|
3
3
|
export declare const numberRe: RegExp;
|
|
4
4
|
export declare function degToRad(angle: number): number;
|
|
5
5
|
export declare function radToDeg(angle: number): number;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const numberRe = /^\s*[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?\s*$/;
|
|
2
|
+
export function degToRad(angle) {
|
|
3
|
+
return (angle / 180) * Math.PI;
|
|
4
|
+
}
|
|
5
|
+
export function radToDeg(angle) {
|
|
6
|
+
return (angle * 180) / Math.PI;
|
|
7
|
+
}
|
|
8
|
+
export function calcDistance(lngLat1, lngLat2) {
|
|
9
|
+
const R = 6371000;
|
|
10
|
+
const rad = Math.PI / 180;
|
|
11
|
+
const lat1 = lngLat1.lat * rad;
|
|
12
|
+
const lat2 = lngLat2.lat * rad;
|
|
13
|
+
const a = Math.sin(lat1) * Math.sin(lat2) +
|
|
14
|
+
Math.cos(lat1) * Math.cos(lat2) * Math.cos((lngLat2.lng - lngLat1.lng) * rad);
|
|
15
|
+
return R * Math.acos(Math.min(a, 1));
|
|
16
|
+
}
|
|
17
|
+
export function stringToId(str) {
|
|
18
|
+
let hash = 0;
|
|
19
|
+
for (let i = 0; i < str.length; i++) {
|
|
20
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
21
|
+
hash &= hash;
|
|
22
|
+
}
|
|
23
|
+
return Math.abs(hash);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolves a potential binding reference.
|
|
27
|
+
*
|
|
28
|
+
* 1. If the string starts with a $, it is considered a binding reference. The value of the binding
|
|
29
|
+
* is then returned.
|
|
30
|
+
* 2. Otherwise, the string is returned as is.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveBinding(str, bindings) {
|
|
33
|
+
return str.startsWith('$') ? bindingToString(bindings[str.slice(1)]) : str;
|
|
34
|
+
}
|
|
35
|
+
export function bindingToString(binding) {
|
|
36
|
+
return typeof binding === 'string'
|
|
37
|
+
? binding
|
|
38
|
+
: typeof binding === 'number'
|
|
39
|
+
? String(binding)
|
|
40
|
+
: typeof binding === 'boolean'
|
|
41
|
+
? String(binding).toUpperCase()
|
|
42
|
+
: typeof binding === 'object' &&
|
|
43
|
+
binding !== null &&
|
|
44
|
+
(binding.t === 'n' || binding.t === 'd')
|
|
45
|
+
? (binding.w ?? '')
|
|
46
|
+
: '';
|
|
47
|
+
}
|
|
48
|
+
export function bindingToPrimitive(binding) {
|
|
49
|
+
return typeof binding === 'string' && numberRe.test(binding)
|
|
50
|
+
? Number(binding)
|
|
51
|
+
: typeof binding === 'string' || typeof binding === 'number' || typeof binding === 'boolean'
|
|
52
|
+
? binding
|
|
53
|
+
: typeof binding === 'object' && binding !== null && binding.t === 'n'
|
|
54
|
+
? binding.v
|
|
55
|
+
: null;
|
|
56
|
+
}
|
|
57
|
+
/* eslint-enable no-nested-ternary */
|
|
58
|
+
export function getYoutubeVideoId(url) {
|
|
59
|
+
url = url.trim();
|
|
60
|
+
// Reference prefixed with a $ or video id
|
|
61
|
+
if (/^\$?[\w\-_]+$/.test(url)) {
|
|
62
|
+
return url;
|
|
63
|
+
}
|
|
64
|
+
const regex = /(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)&?/;
|
|
65
|
+
return (regex.exec(url) || [])[1];
|
|
66
|
+
}
|
|
67
|
+
const baseDpi = 72;
|
|
68
|
+
export function unitConvert(from, to, value) {
|
|
69
|
+
if (from === to) {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
// Converters from MM to unit
|
|
73
|
+
const map = {
|
|
74
|
+
millimeter: 1,
|
|
75
|
+
centimeter: 0.1,
|
|
76
|
+
meter: 0.001,
|
|
77
|
+
kilometer: 0.000001,
|
|
78
|
+
foot: 0.00328084,
|
|
79
|
+
mile: 6.21371e-7,
|
|
80
|
+
nauticalMile: 5.39957e-7,
|
|
81
|
+
inch: 0.0393701,
|
|
82
|
+
pixel: 0.0393701 * baseDpi,
|
|
83
|
+
pica: 0.236222,
|
|
84
|
+
};
|
|
85
|
+
return (value / map[from]) * map[to];
|
|
86
|
+
}
|
|
87
|
+
export function lerp(value1, value2, ratio) {
|
|
88
|
+
return value1 + (value2 - value1) * ratio;
|
|
89
|
+
}
|
|
90
|
+
export function unlerp(min, max, value) {
|
|
91
|
+
return (value - min) / (max - min);
|
|
92
|
+
}
|
|
93
|
+
export function clamp(value, min, max) {
|
|
94
|
+
return Math.max(Math.min(value, max), min);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Fast hash function for non-cryptographic use
|
|
98
|
+
*/
|
|
99
|
+
export function fnv32b(str) {
|
|
100
|
+
const FNV1_32A_INIT = 0x811c9dc5;
|
|
101
|
+
let hash = str
|
|
102
|
+
.split('')
|
|
103
|
+
.map(x => x.charCodeAt(0))
|
|
104
|
+
.reduce((sum, val) => {
|
|
105
|
+
sum ^= val;
|
|
106
|
+
return sum + (sum << 1) + (sum << 4) + (sum << 7) + (sum << 8) + (sum << 24);
|
|
107
|
+
}, FNV1_32A_INIT);
|
|
108
|
+
// Avalanche
|
|
109
|
+
hash ^= hash << 3;
|
|
110
|
+
hash += hash >> 5;
|
|
111
|
+
hash ^= hash << 4;
|
|
112
|
+
hash += hash >> 17;
|
|
113
|
+
hash ^= hash << 25;
|
|
114
|
+
hash += hash >> 6;
|
|
115
|
+
return `0000000${(hash >>> 0).toString(16)}`.substr(-8);
|
|
116
|
+
}
|