@mapcreator/sdk 0.0.8 → 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} +1 -1
- 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 +3 -3
- 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 -3
- 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,351 @@
|
|
|
1
|
+
import { area } from '@turf/area';
|
|
2
|
+
import { coordEach } from '@turf/meta';
|
|
3
|
+
import { feature } from 'topojson-client';
|
|
4
|
+
import { intersect } from '@turf/intersect';
|
|
5
|
+
import { lerp } from '@/utils/helpers';
|
|
6
|
+
import { featureCollection, lineString, polygon } from '@turf/helpers';
|
|
7
|
+
import { geoPath, geoMercator, geoGraticule, geoOrthographic, } from 'd3-geo';
|
|
8
|
+
// Inset map size at scale=1
|
|
9
|
+
const baseSize = 100;
|
|
10
|
+
export function useInsetMap(insetMap, map, cdnUrl) {
|
|
11
|
+
const container = document.createElement('div');
|
|
12
|
+
container.className = 'adornment';
|
|
13
|
+
const scale = insetMap.scale ?? 1;
|
|
14
|
+
const size = scale * baseSize;
|
|
15
|
+
container.style.width = `${size}px`;
|
|
16
|
+
container.style.height = `${size}px`;
|
|
17
|
+
container.style.flexGrow = '0';
|
|
18
|
+
container.style.flexShrink = '0';
|
|
19
|
+
// Creating context
|
|
20
|
+
const context = {
|
|
21
|
+
id: insetMap.id,
|
|
22
|
+
map,
|
|
23
|
+
container,
|
|
24
|
+
moveEndPosition: map.getCenter(),
|
|
25
|
+
geoJson: undefined,
|
|
26
|
+
isGlobe: insetMap.isGlobe,
|
|
27
|
+
boundingBox: insetMap.boundingBox,
|
|
28
|
+
scale,
|
|
29
|
+
backgroundColor: insetMap.backgroundColor ?? '#000000',
|
|
30
|
+
globeBackgroundColor: insetMap.globeBackgroundColor ?? '#000000',
|
|
31
|
+
borderColor: insetMap.borderColor ?? '#000000',
|
|
32
|
+
primaryFillColor: insetMap.primaryFillColor ?? '#000000',
|
|
33
|
+
primaryStrokeColor: insetMap.primaryStrokeColor ?? '#000000',
|
|
34
|
+
secondaryFillColor: insetMap.secondaryFillColor ?? '#000000',
|
|
35
|
+
secondaryStrokeColor: insetMap.secondaryStrokeColor ?? '#000000',
|
|
36
|
+
graticuleColor: insetMap.graticuleColor ?? '#000000',
|
|
37
|
+
frameColor: insetMap.frameColor ?? '#000000',
|
|
38
|
+
frameOutlineColor: insetMap.frameOutlineColor ?? '#000000',
|
|
39
|
+
dotColor: insetMap.dotColor ?? '#000000',
|
|
40
|
+
dotOutlineColor: insetMap.dotOutlineColor ?? '#000000',
|
|
41
|
+
globeFallbackActive: false,
|
|
42
|
+
globeGeoJsonRequested: false,
|
|
43
|
+
globeGeoJson: undefined,
|
|
44
|
+
};
|
|
45
|
+
// Initializing data download
|
|
46
|
+
fetch(`${cdnUrl}/data/insetmaps/${insetMap.fileName}`)
|
|
47
|
+
.then(response => response.json())
|
|
48
|
+
.then(topology => {
|
|
49
|
+
// Once downloaded, add data to context and rerender
|
|
50
|
+
context.geoJson = feature(topology, Object.keys(topology.objects)[0]);
|
|
51
|
+
render(context);
|
|
52
|
+
});
|
|
53
|
+
map.on('moveend', () => {
|
|
54
|
+
context.moveEndPosition = map.getCenter();
|
|
55
|
+
// Only do a full rerender at moveend if we're rendering a globe
|
|
56
|
+
if (context.isGlobe || context.globeFallbackActive) {
|
|
57
|
+
render(context);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
map.on('move', () => {
|
|
61
|
+
// Non-globe inset map: fallback to globe logic
|
|
62
|
+
if (!context.isGlobe) {
|
|
63
|
+
const [minLng, minLat, maxLng, maxLat] = context.boundingBox;
|
|
64
|
+
const frameGeometry = calcAspectBoxGeometry(map);
|
|
65
|
+
const insetMapGeometry = polygon([
|
|
66
|
+
[
|
|
67
|
+
[minLng, minLat],
|
|
68
|
+
[minLng, maxLat],
|
|
69
|
+
[maxLng, maxLat],
|
|
70
|
+
[maxLng, minLat],
|
|
71
|
+
[minLng, minLat],
|
|
72
|
+
],
|
|
73
|
+
]);
|
|
74
|
+
const intersection = intersect(featureCollection([frameGeometry, insetMapGeometry]));
|
|
75
|
+
const frameArea = area(frameGeometry);
|
|
76
|
+
const intersectionArea = intersection ? area(intersection) : 0;
|
|
77
|
+
// We consider to be out of inset map bounds if more than 25% of the frame is outside of the map
|
|
78
|
+
const outOfBounds = intersectionArea < frameArea * 0.75;
|
|
79
|
+
// Fallback turned on or off: full rerender
|
|
80
|
+
if (context.globeFallbackActive !== outOfBounds) {
|
|
81
|
+
context.globeFallbackActive = outOfBounds;
|
|
82
|
+
context.moveEndPosition = map.getCenter();
|
|
83
|
+
render(context);
|
|
84
|
+
}
|
|
85
|
+
// Request fallback globe data if needed
|
|
86
|
+
if (context.globeFallbackActive && !context.globeGeoJsonRequested) {
|
|
87
|
+
context.globeGeoJsonRequested = true;
|
|
88
|
+
fetch(`${cdnUrl}/data/insetmaps/world-110m.json`)
|
|
89
|
+
.then(response => response.json())
|
|
90
|
+
.then(topology => {
|
|
91
|
+
// Once downloaded, do a full rerender
|
|
92
|
+
context.globeGeoJson = feature(topology, Object.keys(topology.objects)[0]);
|
|
93
|
+
context.moveEndPosition = map.getCenter();
|
|
94
|
+
render(context);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Always update frame highlight on move
|
|
99
|
+
positionHighlight(context);
|
|
100
|
+
});
|
|
101
|
+
return container;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Full rerender of the inset map
|
|
105
|
+
*/
|
|
106
|
+
function render(context) {
|
|
107
|
+
const { id, container, scale, backgroundColor, globeBackgroundColor, secondaryFillColor, secondaryStrokeColor, primaryFillColor, primaryStrokeColor, graticuleColor, borderColor, frameColor, frameOutlineColor, dotColor, dotOutlineColor, } = context;
|
|
108
|
+
// Decide if we’re rendering globe as fallback
|
|
109
|
+
const globeFallback = context.globeFallbackActive && context.globeGeoJson !== undefined;
|
|
110
|
+
const geoJson = globeFallback ? context.globeGeoJson : context.geoJson;
|
|
111
|
+
const isGlobe = globeFallback || context.isGlobe;
|
|
112
|
+
// Data not downloaded yet
|
|
113
|
+
if (geoJson === undefined) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const size = baseSize * scale;
|
|
117
|
+
const halfSize = size / 2;
|
|
118
|
+
const lineWidth = 0.1 * scale;
|
|
119
|
+
// Background nodes
|
|
120
|
+
const nodes = [
|
|
121
|
+
isGlobe
|
|
122
|
+
? `<circle cx="${halfSize}" cy="${halfSize}" r="${halfSize}" fill="${globeBackgroundColor}"></circle>`
|
|
123
|
+
: `<rect x="0" y="0" width="${size}" height="${size}" fill="${backgroundColor}"></rect>`,
|
|
124
|
+
];
|
|
125
|
+
const { path } = getPathAndProjection(context);
|
|
126
|
+
// Secondary polygons
|
|
127
|
+
const dSecondary = path({
|
|
128
|
+
...geoJson,
|
|
129
|
+
features: geoJson.features.filter(f => f.properties?.class !== 'P1'),
|
|
130
|
+
});
|
|
131
|
+
if (dSecondary) {
|
|
132
|
+
nodes.push(`
|
|
133
|
+
<path
|
|
134
|
+
d="${dSecondary}"
|
|
135
|
+
fill="${secondaryFillColor}"
|
|
136
|
+
stroke="${secondaryStrokeColor}"
|
|
137
|
+
stroke-width="${lineWidth}"
|
|
138
|
+
></path>
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
// Primary polygons
|
|
142
|
+
const dPrimary = path({
|
|
143
|
+
...geoJson,
|
|
144
|
+
features: geoJson.features.filter(f => f.properties?.class === 'P1'),
|
|
145
|
+
});
|
|
146
|
+
if (dPrimary) {
|
|
147
|
+
nodes.push(`
|
|
148
|
+
<path
|
|
149
|
+
d="${dPrimary}"
|
|
150
|
+
fill="${primaryFillColor}"
|
|
151
|
+
stroke="${primaryStrokeColor}"
|
|
152
|
+
stroke-width="${lineWidth}"
|
|
153
|
+
></path>
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
// Graticules (for globe)
|
|
157
|
+
if (isGlobe) {
|
|
158
|
+
const dGraticule = path(featureCollection(geoGraticule()
|
|
159
|
+
.step([10, 10])
|
|
160
|
+
.lines()
|
|
161
|
+
.map(geometry => lineString(geometry.coordinates))));
|
|
162
|
+
nodes.push(`
|
|
163
|
+
<path
|
|
164
|
+
d="${dGraticule}"
|
|
165
|
+
fill="none"
|
|
166
|
+
stroke="${graticuleColor}"
|
|
167
|
+
stroke-width="${lineWidth}"
|
|
168
|
+
></path>
|
|
169
|
+
`);
|
|
170
|
+
}
|
|
171
|
+
// Frame highlight nodes. Geometries and visibility will be set in positionHighlight
|
|
172
|
+
nodes.push(`
|
|
173
|
+
<path
|
|
174
|
+
id="frame-outline-${id}"
|
|
175
|
+
fill="none"
|
|
176
|
+
stroke="${frameOutlineColor}"
|
|
177
|
+
stroke-width="${4 * scale}"
|
|
178
|
+
></path>
|
|
179
|
+
`);
|
|
180
|
+
nodes.push(`
|
|
181
|
+
<path
|
|
182
|
+
id="frame-${id}"
|
|
183
|
+
fill="none"
|
|
184
|
+
stroke="${frameColor}"
|
|
185
|
+
stroke-width="${2 * scale}"
|
|
186
|
+
></path>
|
|
187
|
+
`);
|
|
188
|
+
nodes.push(`
|
|
189
|
+
<circle
|
|
190
|
+
id="dot-${id}"
|
|
191
|
+
r="${5 * scale}"
|
|
192
|
+
fill="${dotColor}"
|
|
193
|
+
stroke="${dotOutlineColor}"
|
|
194
|
+
stroke-width="${2 * scale}"
|
|
195
|
+
></circle>
|
|
196
|
+
`);
|
|
197
|
+
// Inset map border (non-globe only)
|
|
198
|
+
if (!isGlobe) {
|
|
199
|
+
nodes.push(`
|
|
200
|
+
<rect
|
|
201
|
+
x="${scale / 2}"
|
|
202
|
+
y="${scale / 2}"
|
|
203
|
+
width="${size - scale}"
|
|
204
|
+
height="${size - scale}"
|
|
205
|
+
fill="none"
|
|
206
|
+
stroke="${borderColor}"
|
|
207
|
+
stroke-width="${scale}"
|
|
208
|
+
></rect>
|
|
209
|
+
`);
|
|
210
|
+
}
|
|
211
|
+
const svg = `
|
|
212
|
+
<svg
|
|
213
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
214
|
+
viewBox="0 0 ${size} ${size}"
|
|
215
|
+
width="${size}"
|
|
216
|
+
height="${size}"
|
|
217
|
+
>
|
|
218
|
+
${nodes.join('')}
|
|
219
|
+
</svg>
|
|
220
|
+
`;
|
|
221
|
+
container.innerHTML = svg;
|
|
222
|
+
positionHighlight(context);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Calculates D3 Projection and Path generator
|
|
226
|
+
*/
|
|
227
|
+
function getPathAndProjection(context) {
|
|
228
|
+
const { scale, boundingBox, moveEndPosition } = context;
|
|
229
|
+
// Decide if we’re rendering a globe
|
|
230
|
+
const globeFallback = context.globeFallbackActive && context.globeGeoJson !== undefined;
|
|
231
|
+
const isGlobe = globeFallback || context.isGlobe;
|
|
232
|
+
const size = baseSize * scale;
|
|
233
|
+
const path = geoPath();
|
|
234
|
+
let projection;
|
|
235
|
+
if (isGlobe) {
|
|
236
|
+
const { lng, lat } = moveEndPosition;
|
|
237
|
+
projection = geoOrthographic()
|
|
238
|
+
.scale(size / 2)
|
|
239
|
+
.translate([size / 2, size / 2])
|
|
240
|
+
.rotate([-lng, -lat])
|
|
241
|
+
.clipAngle(90);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
projection = geoMercator().scale(1).translate([0, 0]);
|
|
245
|
+
const topLeft = projection([boundingBox[0], boundingBox[3]]);
|
|
246
|
+
const bottomRight = projection([boundingBox[2], boundingBox[1]]);
|
|
247
|
+
const dx = Math.abs(bottomRight[0] - topLeft[0]);
|
|
248
|
+
const dy = Math.abs(bottomRight[1] - topLeft[1]);
|
|
249
|
+
const x = (topLeft[0] + bottomRight[0]) / 2;
|
|
250
|
+
const y = (topLeft[1] + bottomRight[1]) / 2;
|
|
251
|
+
const scaleFactor = 1 / Math.max(dx / size, dy / size);
|
|
252
|
+
projection
|
|
253
|
+
.scale(scaleFactor)
|
|
254
|
+
.translate([size / 2 - scaleFactor * x, size / 2 - scaleFactor * y]);
|
|
255
|
+
}
|
|
256
|
+
path.projection(projection);
|
|
257
|
+
return { path, projection };
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Updates frame highlight nodes, called on every frame
|
|
261
|
+
*/
|
|
262
|
+
function positionHighlight(context) {
|
|
263
|
+
const { id, map, container, scale } = context;
|
|
264
|
+
const frameNode = container.querySelector(`#frame-${id}`);
|
|
265
|
+
const frameOutlineNode = container.querySelector(`#frame-outline-${id}`);
|
|
266
|
+
const dotNode = container.querySelector(`#dot-${id}`);
|
|
267
|
+
// Map not rendered yet (waiting for data)
|
|
268
|
+
if (!frameNode || !frameOutlineNode || !dotNode) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const frameGeometry = calcAspectBoxGeometry(map);
|
|
272
|
+
const { path, projection } = getPathAndProjection(context);
|
|
273
|
+
let minLng = Infinity;
|
|
274
|
+
let maxLng = -Infinity;
|
|
275
|
+
let minX = Infinity;
|
|
276
|
+
let maxX = -Infinity;
|
|
277
|
+
let minY = Infinity;
|
|
278
|
+
let maxY = -Infinity;
|
|
279
|
+
coordEach(frameGeometry, coord => {
|
|
280
|
+
const point = projection(coord);
|
|
281
|
+
minLng = Math.min(minLng, coord[0]);
|
|
282
|
+
maxLng = Math.max(maxLng, coord[0]);
|
|
283
|
+
minX = Math.min(minX, point[0]);
|
|
284
|
+
maxX = Math.max(maxX, point[0]);
|
|
285
|
+
minY = Math.min(minY, point[1]);
|
|
286
|
+
maxY = Math.max(maxY, point[1]);
|
|
287
|
+
});
|
|
288
|
+
const dotThreshold = 10 * scale;
|
|
289
|
+
if (maxX - minX < dotThreshold || maxY - minY < dotThreshold) {
|
|
290
|
+
// Frame to small: rendering as circle
|
|
291
|
+
frameNode.style.display = 'none';
|
|
292
|
+
frameOutlineNode.style.display = 'none';
|
|
293
|
+
dotNode.style.display = 'block';
|
|
294
|
+
const center = map.getCenter();
|
|
295
|
+
const [x, y] = projection([center.lng, center.lat]);
|
|
296
|
+
dotNode.setAttribute('cx', String(x));
|
|
297
|
+
dotNode.setAttribute('cy', String(y));
|
|
298
|
+
}
|
|
299
|
+
else if (maxLng - minLng < 180) {
|
|
300
|
+
// Frame not to big: rendering as polygon
|
|
301
|
+
frameNode.style.display = 'block';
|
|
302
|
+
frameOutlineNode.style.display = 'block';
|
|
303
|
+
dotNode.style.display = 'none';
|
|
304
|
+
const d = path(frameGeometry);
|
|
305
|
+
frameNode.setAttribute('d', d);
|
|
306
|
+
frameOutlineNode.setAttribute('d', d);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
// Frame too large: not rendering at all
|
|
310
|
+
frameNode.style.display = 'none';
|
|
311
|
+
frameOutlineNode.style.display = 'none';
|
|
312
|
+
dotNode.style.display = 'none';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Calculates GeoJSON Polygon geometry of the map viewport
|
|
317
|
+
*/
|
|
318
|
+
function calcAspectBoxGeometry(map) {
|
|
319
|
+
const leftX = 0;
|
|
320
|
+
const topY = 0;
|
|
321
|
+
const rightX = map.getCanvas().width;
|
|
322
|
+
const bottomY = map.getCanvas().height;
|
|
323
|
+
const precision = 64;
|
|
324
|
+
const coordinates = [];
|
|
325
|
+
for (let i = 0; i < precision; i++) {
|
|
326
|
+
const { lng, lat } = map.unproject([lerp(leftX, rightX, i / precision), topY]);
|
|
327
|
+
coordinates.push([lng, lat]);
|
|
328
|
+
}
|
|
329
|
+
for (let i = 0; i < precision; i++) {
|
|
330
|
+
const { lng, lat } = map.unproject([rightX, lerp(topY, bottomY, i / precision)]);
|
|
331
|
+
coordinates.push([lng, lat]);
|
|
332
|
+
}
|
|
333
|
+
for (let i = 0; i < precision; i++) {
|
|
334
|
+
const { lng, lat } = map.unproject([lerp(rightX, leftX, i / precision), bottomY]);
|
|
335
|
+
coordinates.push([lng, lat]);
|
|
336
|
+
}
|
|
337
|
+
for (let i = 0; i < precision; i++) {
|
|
338
|
+
const { lng, lat } = map.unproject([leftX, lerp(bottomY, topY, i / precision)]);
|
|
339
|
+
coordinates.push([lng, lat]);
|
|
340
|
+
}
|
|
341
|
+
const { lng, lat } = map.unproject([leftX, topY]);
|
|
342
|
+
coordinates.push([lng, lat]);
|
|
343
|
+
return {
|
|
344
|
+
type: 'Feature',
|
|
345
|
+
properties: {},
|
|
346
|
+
geometry: {
|
|
347
|
+
coordinates: [coordinates],
|
|
348
|
+
type: 'Polygon',
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { JobObjectManualLegend } from '
|
|
1
|
+
import type { JobObjectManualLegend } from '@/types/jobObject';
|
|
2
2
|
export declare function useManualLegend(legend: JobObjectManualLegend, vapiUrl: string, accessToken: string): HTMLElement;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadFonts } from '@/utils/svgHelpers';
|
|
2
|
+
import { getCategoricalLegendSvg } from '@/adornments/categoricalLegend';
|
|
3
|
+
export function useManualLegend(legend, vapiUrl, accessToken) {
|
|
4
|
+
const container = document.createElement('div');
|
|
5
|
+
container.className = 'adornment';
|
|
6
|
+
if (legend.showBackground) {
|
|
7
|
+
container.style.boxShadow = '0 0 7px 0 rgba(33, 37, 41, 0.25)';
|
|
8
|
+
container.style.borderRadius = '10px';
|
|
9
|
+
}
|
|
10
|
+
const defaultFont = 'ArialMT';
|
|
11
|
+
const fonts = [legend.titleFont ?? defaultFont, legend.entryFont ?? defaultFont];
|
|
12
|
+
const fontsToLoad = Array.from(new Set(fonts));
|
|
13
|
+
loadFonts(fontsToLoad, vapiUrl, accessToken).then(() => (container.innerHTML = getCategoricalLegendSvg(legend, defaultFont)));
|
|
14
|
+
return container;
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function useNorthArrow(northArrow, map) {
|
|
2
|
+
const container = document.createElement('div');
|
|
3
|
+
container.className = 'adornment';
|
|
4
|
+
const size = (northArrow.scale ?? 1) * 30;
|
|
5
|
+
container.style.cssText = `width: ${size}px; height: ${size}px`;
|
|
6
|
+
map.on('move', () => {
|
|
7
|
+
container.innerHTML = getSvg(map, size);
|
|
8
|
+
});
|
|
9
|
+
container.innerHTML = getSvg(map, size);
|
|
10
|
+
return container;
|
|
11
|
+
}
|
|
12
|
+
function getSvg(map, size) {
|
|
13
|
+
const rotation = map.getBearing();
|
|
14
|
+
const pitch = map.getPitch();
|
|
15
|
+
return `
|
|
16
|
+
<svg width="${size}" height="${size}" viewBox="-5 -5 30 30" xmlns="http://www.w3.org/2000/svg">
|
|
17
|
+
<g class="tilt" style="transform-origin: 33% 33%; transform: rotateX(${pitch}deg);">
|
|
18
|
+
<g class="rotate" style="transform-origin: 33% 33%; transform: rotate(${-rotation}deg);">
|
|
19
|
+
<path d="M0 20L10 0L20 20L10 12.7711L0 20Z" fill="#343A40" />
|
|
20
|
+
</g>
|
|
21
|
+
</g>
|
|
22
|
+
</svg>
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { calcDistance, unitConvert } from '@/utils/helpers';
|
|
2
|
+
import { floatToFraction, getRoundNum } from '@/utils/scalebar';
|
|
3
|
+
const units = {
|
|
4
|
+
kilometer: 'km',
|
|
5
|
+
meter: 'm',
|
|
6
|
+
mile: 'mi',
|
|
7
|
+
foot: 'ft',
|
|
8
|
+
nauticalMile: 'nm',
|
|
9
|
+
};
|
|
10
|
+
export function useScalebar(scalebar, map) {
|
|
11
|
+
const container = document.createElement('div');
|
|
12
|
+
container.className = 'adornment';
|
|
13
|
+
container.innerHTML = getSvg(map, scalebar);
|
|
14
|
+
map.on('move', () => {
|
|
15
|
+
container.innerHTML = getSvg(map, scalebar);
|
|
16
|
+
});
|
|
17
|
+
return container;
|
|
18
|
+
}
|
|
19
|
+
function getSvg(map, scalebar) {
|
|
20
|
+
const { maxWidth = 100, fontSize = 8, font = 'Arial, sans-serif', color = '#000000', unit = 'metric_and_imperial', } = scalebar;
|
|
21
|
+
const center = map.getCenter();
|
|
22
|
+
const point = map.project(center);
|
|
23
|
+
const maxMeters = calcDistance(center, map.unproject([point.x + maxWidth, point.y]));
|
|
24
|
+
const scaleData = Object.fromEntries(Object.entries(units).map(([u, abbr]) => {
|
|
25
|
+
const value = unitConvert('meter', u, maxMeters);
|
|
26
|
+
const dist = getRoundNum(value) || 0;
|
|
27
|
+
const ratio = value ? dist / value : 0;
|
|
28
|
+
const humanized = dist < 1 ? `${floatToFraction(dist).join('/')} ${abbr}` : `${dist} ${abbr}`;
|
|
29
|
+
return [u, { value, ratio, humanized }];
|
|
30
|
+
}));
|
|
31
|
+
const scaleMetric = scaleData.meter.value > 1000 ? scaleData.kilometer : scaleData.meter;
|
|
32
|
+
const scaleImperial = scaleData.foot.value > 5280 ? scaleData.mile : scaleData.foot;
|
|
33
|
+
const scaleMap = {
|
|
34
|
+
imperial: scaleImperial,
|
|
35
|
+
metric: scaleMetric,
|
|
36
|
+
nautical: scaleData.nauticalMile,
|
|
37
|
+
};
|
|
38
|
+
const scaleSingle = unit !== 'metric_and_imperial' ? scaleMap[unit] : undefined;
|
|
39
|
+
const scaleLengths = {
|
|
40
|
+
metric: Math.round(maxWidth * scaleMetric.ratio),
|
|
41
|
+
imperial: Math.round(maxWidth * scaleImperial.ratio),
|
|
42
|
+
};
|
|
43
|
+
const mainBarLength = unit === 'metric_and_imperial'
|
|
44
|
+
? Math.max(scaleLengths.metric, scaleLengths.imperial)
|
|
45
|
+
: Math.round(maxWidth * (scaleSingle?.ratio ?? 0));
|
|
46
|
+
const baseY = fontSize + 7;
|
|
47
|
+
if (unit === 'metric_and_imperial') {
|
|
48
|
+
return `
|
|
49
|
+
<svg
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
51
|
+
width="${mainBarLength}"
|
|
52
|
+
height="${baseY + fontSize * 2}"
|
|
53
|
+
>
|
|
54
|
+
<line
|
|
55
|
+
x1="0"
|
|
56
|
+
y1="${baseY - 0.5}"
|
|
57
|
+
x2="${mainBarLength}"
|
|
58
|
+
y2="${baseY - 0.5}"
|
|
59
|
+
stroke="${color}"
|
|
60
|
+
stroke-width="1"
|
|
61
|
+
/>
|
|
62
|
+
<line
|
|
63
|
+
x1="0.5"
|
|
64
|
+
y1="${baseY - 5}"
|
|
65
|
+
x2="0.5"
|
|
66
|
+
y2="${baseY + 5}"
|
|
67
|
+
stroke="${color}"
|
|
68
|
+
stroke-width="1"
|
|
69
|
+
/>
|
|
70
|
+
<line
|
|
71
|
+
x1="${scaleLengths.imperial - 0.5}"
|
|
72
|
+
y1="${baseY - 5}"
|
|
73
|
+
x2="${scaleLengths.imperial - 0.5}"
|
|
74
|
+
y2="${baseY}"
|
|
75
|
+
stroke="${color}"
|
|
76
|
+
stroke-width="1"
|
|
77
|
+
/>
|
|
78
|
+
<line
|
|
79
|
+
x1="${scaleLengths.metric - 0.5}"
|
|
80
|
+
y1="${baseY}"
|
|
81
|
+
x2="${scaleLengths.metric - 0.5}"
|
|
82
|
+
y2="${baseY + 5}"
|
|
83
|
+
stroke="${color}"
|
|
84
|
+
stroke-width="1"
|
|
85
|
+
/>
|
|
86
|
+
<text
|
|
87
|
+
x="0"
|
|
88
|
+
y="${baseY + 7 + fontSize}"
|
|
89
|
+
font-size="${fontSize}px"
|
|
90
|
+
font-family="${font}"
|
|
91
|
+
font-weight="bold"
|
|
92
|
+
fill="${color}"
|
|
93
|
+
text-anchor="start"
|
|
94
|
+
>
|
|
95
|
+
0
|
|
96
|
+
</text>
|
|
97
|
+
<text
|
|
98
|
+
x="${scaleLengths.imperial}"
|
|
99
|
+
y="${baseY - 7}"
|
|
100
|
+
font-size="${fontSize}px"
|
|
101
|
+
font-family="${font}"
|
|
102
|
+
font-weight="bold"
|
|
103
|
+
fill="${color}"
|
|
104
|
+
text-anchor="end"
|
|
105
|
+
>
|
|
106
|
+
${scaleImperial.humanized}
|
|
107
|
+
</text>
|
|
108
|
+
<text
|
|
109
|
+
x="${scaleLengths.metric}"
|
|
110
|
+
y="${baseY + 7 + fontSize}"
|
|
111
|
+
font-size="${fontSize}px"
|
|
112
|
+
font-family="${font}"
|
|
113
|
+
font-weight="bold"
|
|
114
|
+
fill="${color}"
|
|
115
|
+
text-anchor="end"
|
|
116
|
+
>
|
|
117
|
+
${scaleMetric.humanized}
|
|
118
|
+
</text>
|
|
119
|
+
</svg>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
return `
|
|
123
|
+
<svg
|
|
124
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
125
|
+
width="${mainBarLength}"
|
|
126
|
+
height="${baseY + fontSize + 5}"
|
|
127
|
+
>
|
|
128
|
+
<line
|
|
129
|
+
x1="0"
|
|
130
|
+
y1="4.5"
|
|
131
|
+
x2="${mainBarLength}"
|
|
132
|
+
y2="4.5"
|
|
133
|
+
stroke="${color}"
|
|
134
|
+
stroke-width="1"
|
|
135
|
+
/>
|
|
136
|
+
<line
|
|
137
|
+
x1="0.5"
|
|
138
|
+
y1="0"
|
|
139
|
+
x2="0.5"
|
|
140
|
+
y2="10"
|
|
141
|
+
stroke="${color}"
|
|
142
|
+
stroke-width="1"
|
|
143
|
+
/>
|
|
144
|
+
<line
|
|
145
|
+
x1="${mainBarLength - 0.5}"
|
|
146
|
+
y1="0"
|
|
147
|
+
x2="${mainBarLength - 0.5}"
|
|
148
|
+
y2="10"
|
|
149
|
+
stroke="${color}"
|
|
150
|
+
stroke-width="1"
|
|
151
|
+
/>
|
|
152
|
+
<text
|
|
153
|
+
x="0"
|
|
154
|
+
y="${fontSize + 12}"
|
|
155
|
+
font-size="${fontSize}px"
|
|
156
|
+
font-family="${font}"
|
|
157
|
+
font-weight="bold"
|
|
158
|
+
fill="${color}"
|
|
159
|
+
text-anchor="start"
|
|
160
|
+
>
|
|
161
|
+
0
|
|
162
|
+
</text>
|
|
163
|
+
<text
|
|
164
|
+
x="${mainBarLength}"
|
|
165
|
+
y="${fontSize + 12}"
|
|
166
|
+
font-size="${fontSize}px"
|
|
167
|
+
font-family="${font}"
|
|
168
|
+
font-weight="bold"
|
|
169
|
+
fill="${color}"
|
|
170
|
+
text-anchor="end"
|
|
171
|
+
>
|
|
172
|
+
${scaleSingle?.humanized ?? ''}
|
|
173
|
+
</text>
|
|
174
|
+
</svg>
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AdornmentPosition } from '
|
|
2
|
-
import { SkySpecification } from '@mapcreator/maplibre-gl';
|
|
1
|
+
import type { AdornmentPosition } from '@/types/jobObject';
|
|
2
|
+
import type { SkySpecification } from '@mapcreator/maplibre-gl';
|
|
3
3
|
export declare const defaultSky: SkySpecification;
|
|
4
4
|
type PositionConfig = {
|
|
5
5
|
x: 'left' | 'right' | 'center';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const defaultSky = {
|
|
2
|
+
'sky-color': '#6babff',
|
|
3
|
+
'sky-horizon-blend': 1,
|
|
4
|
+
'horizon-color': [
|
|
5
|
+
'interpolate-hcl',
|
|
6
|
+
['linear'],
|
|
7
|
+
['zoom'],
|
|
8
|
+
0,
|
|
9
|
+
'#edf7fc',
|
|
10
|
+
9,
|
|
11
|
+
'#edf7fc',
|
|
12
|
+
10,
|
|
13
|
+
'#dcf0fa',
|
|
14
|
+
],
|
|
15
|
+
'horizon-fog-blend': 0.5,
|
|
16
|
+
'fog-color': '#edf7fc',
|
|
17
|
+
'fog-ground-blend': 0.5,
|
|
18
|
+
'atmosphere-blend': 1,
|
|
19
|
+
};
|
|
20
|
+
export const positionConfig = {
|
|
21
|
+
top_left: {
|
|
22
|
+
x: 'left',
|
|
23
|
+
y: 'top',
|
|
24
|
+
},
|
|
25
|
+
top_center: {
|
|
26
|
+
x: 'center',
|
|
27
|
+
y: 'top',
|
|
28
|
+
},
|
|
29
|
+
top_right: {
|
|
30
|
+
x: 'right',
|
|
31
|
+
y: 'top',
|
|
32
|
+
},
|
|
33
|
+
right_center: {
|
|
34
|
+
x: 'right',
|
|
35
|
+
y: 'center',
|
|
36
|
+
},
|
|
37
|
+
bottom_right: {
|
|
38
|
+
x: 'right',
|
|
39
|
+
y: 'bottom',
|
|
40
|
+
},
|
|
41
|
+
bottom_center: {
|
|
42
|
+
x: 'center',
|
|
43
|
+
y: 'bottom',
|
|
44
|
+
},
|
|
45
|
+
bottom_left: {
|
|
46
|
+
x: 'left',
|
|
47
|
+
y: 'bottom',
|
|
48
|
+
},
|
|
49
|
+
left_center: {
|
|
50
|
+
x: 'left',
|
|
51
|
+
y: 'center',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Map } from '@mapcreator/maplibre-gl';
|
|
1
|
+
import type { Map } from '@mapcreator/maplibre-gl';
|
|
2
2
|
export declare function useFullScreenButton(map: Map): HTMLElement;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fullScreenIcon from '@/images/full-screen.svg?raw';
|
|
2
|
+
import { useButtonControl } from '@/controls/controls';
|
|
3
|
+
import { isFullscreenSupported, enterFullscreen, exitFullscreen, isFullscreen, } from '@/utils/fullscreen';
|
|
4
|
+
export function useFullScreenButton(map) {
|
|
5
|
+
const onClick = () => {
|
|
6
|
+
const mapContainer = map.getContainer();
|
|
7
|
+
if (isFullscreenSupported()) {
|
|
8
|
+
if (isFullscreen(mapContainer)) {
|
|
9
|
+
exitFullscreen();
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
enterFullscreen(mapContainer);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
let url = document.location.href;
|
|
17
|
+
url += url.includes('?') ? '&' : '?';
|
|
18
|
+
url += 'close-tab';
|
|
19
|
+
window.open(url, '_blank');
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const control = {
|
|
23
|
+
type: 'button',
|
|
24
|
+
className: 'control-button fullscreen-control-button',
|
|
25
|
+
content: fullScreenIcon,
|
|
26
|
+
onClick,
|
|
27
|
+
};
|
|
28
|
+
return useButtonControl(control);
|
|
29
|
+
}
|