@page-speed/maps 0.1.9 → 0.2.1
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/components/geo-map.cjs +166 -31
- package/dist/components/geo-map.cjs.map +1 -1
- package/dist/components/geo-map.js +167 -32
- package/dist/components/geo-map.js.map +1 -1
- package/dist/components/index.cjs +166 -31
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +167 -32
- package/dist/components/index.js.map +1 -1
- package/dist/index.cjs +98 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +98 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React3 from 'react';
|
|
2
|
-
import React3__default from 'react';
|
|
2
|
+
import React3__default, { useMemo } from 'react';
|
|
3
3
|
import { Marker, Map as Map$1, GeolocateControl, NavigationControl } from 'react-map-gl/maplibre';
|
|
4
4
|
import { clsx } from 'clsx';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -518,12 +518,93 @@ function MapLibre({
|
|
|
518
518
|
}
|
|
519
519
|
);
|
|
520
520
|
}
|
|
521
|
+
var TILE_SIZE = 512;
|
|
522
|
+
function latToMercatorY(lat) {
|
|
523
|
+
const latRad = lat * Math.PI / 180;
|
|
524
|
+
const mercN = Math.log(Math.tan(Math.PI / 4 + latRad / 2));
|
|
525
|
+
return TILE_SIZE / (2 * Math.PI) * (Math.PI - mercN);
|
|
526
|
+
}
|
|
527
|
+
function lngToMercatorX(lng) {
|
|
528
|
+
return TILE_SIZE / (2 * Math.PI) * ((lng + 180) / 360 * 2 * Math.PI);
|
|
529
|
+
}
|
|
530
|
+
function computeDefaultZoom(options) {
|
|
531
|
+
const {
|
|
532
|
+
coordinates,
|
|
533
|
+
mapWidth,
|
|
534
|
+
mapHeight,
|
|
535
|
+
padding = 50,
|
|
536
|
+
maxZoom = 18,
|
|
537
|
+
minZoom = 1
|
|
538
|
+
} = options;
|
|
539
|
+
if (coordinates.length === 0) return null;
|
|
540
|
+
if (coordinates.length === 1) return maxZoom;
|
|
541
|
+
if (mapWidth <= 0 || mapHeight <= 0) return null;
|
|
542
|
+
let minLat = Infinity;
|
|
543
|
+
let maxLat = -Infinity;
|
|
544
|
+
let minLng = Infinity;
|
|
545
|
+
let maxLng = -Infinity;
|
|
546
|
+
for (const coord of coordinates) {
|
|
547
|
+
if (coord.lat < minLat) minLat = coord.lat;
|
|
548
|
+
if (coord.lat > maxLat) maxLat = coord.lat;
|
|
549
|
+
if (coord.lng < minLng) minLng = coord.lng;
|
|
550
|
+
if (coord.lng > maxLng) maxLng = coord.lng;
|
|
551
|
+
}
|
|
552
|
+
const pixelXMin = lngToMercatorX(minLng);
|
|
553
|
+
const pixelXMax = lngToMercatorX(maxLng);
|
|
554
|
+
const pixelYMin = latToMercatorY(maxLat);
|
|
555
|
+
const pixelYMax = latToMercatorY(minLat);
|
|
556
|
+
const dx = Math.abs(pixelXMax - pixelXMin);
|
|
557
|
+
const dy = Math.abs(pixelYMax - pixelYMin);
|
|
558
|
+
const availableWidth = mapWidth - padding * 2;
|
|
559
|
+
const availableHeight = mapHeight - padding * 2;
|
|
560
|
+
if (availableWidth <= 0 || availableHeight <= 0) return minZoom;
|
|
561
|
+
let zoom;
|
|
562
|
+
if (dx === 0 && dy === 0) {
|
|
563
|
+
return maxZoom;
|
|
564
|
+
} else if (dx === 0) {
|
|
565
|
+
zoom = Math.log2(availableHeight / dy);
|
|
566
|
+
} else if (dy === 0) {
|
|
567
|
+
zoom = Math.log2(availableWidth / dx);
|
|
568
|
+
} else {
|
|
569
|
+
const zoomX = Math.log2(availableWidth / dx);
|
|
570
|
+
const zoomY = Math.log2(availableHeight / dy);
|
|
571
|
+
zoom = Math.min(zoomX, zoomY);
|
|
572
|
+
}
|
|
573
|
+
return Math.max(minZoom, Math.min(maxZoom, Math.floor(zoom * 100) / 100));
|
|
574
|
+
}
|
|
575
|
+
function useDefaultZoom(options) {
|
|
576
|
+
const { coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom } = options;
|
|
577
|
+
return useMemo(
|
|
578
|
+
() => computeDefaultZoom({
|
|
579
|
+
coordinates,
|
|
580
|
+
mapWidth,
|
|
581
|
+
mapHeight,
|
|
582
|
+
padding,
|
|
583
|
+
maxZoom,
|
|
584
|
+
minZoom
|
|
585
|
+
}),
|
|
586
|
+
[coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom]
|
|
587
|
+
);
|
|
588
|
+
}
|
|
521
589
|
var PANEL_POSITION_CLASS = {
|
|
522
590
|
"top-left": "left-4 top-4",
|
|
523
591
|
"top-right": "right-4 top-4",
|
|
524
592
|
"bottom-left": "bottom-4 left-4",
|
|
525
593
|
"bottom-right": "bottom-4 right-4"
|
|
526
594
|
};
|
|
595
|
+
function getOptimalPanelPosition(markerLat, markerLng, mapCenter) {
|
|
596
|
+
const isNorth = markerLat >= mapCenter.latitude;
|
|
597
|
+
const isEast = markerLng >= mapCenter.longitude;
|
|
598
|
+
if (isNorth && isEast) {
|
|
599
|
+
return "bottom-left";
|
|
600
|
+
} else if (isNorth && !isEast) {
|
|
601
|
+
return "bottom-right";
|
|
602
|
+
} else if (!isNorth && isEast) {
|
|
603
|
+
return "top-left";
|
|
604
|
+
} else {
|
|
605
|
+
return "top-right";
|
|
606
|
+
}
|
|
607
|
+
}
|
|
527
608
|
var DEFAULT_VIEW_STATE = {
|
|
528
609
|
latitude: 39.5,
|
|
529
610
|
longitude: -98.35,
|
|
@@ -766,6 +847,13 @@ function GeoMap({
|
|
|
766
847
|
IconComponent = FallbackIcon,
|
|
767
848
|
ImgComponent = FallbackImg
|
|
768
849
|
}) {
|
|
850
|
+
const containerRef = React3.useRef(null);
|
|
851
|
+
const [containerDimensions, setContainerDimensions] = React3.useState({
|
|
852
|
+
width: 800,
|
|
853
|
+
// Default width
|
|
854
|
+
height: 520
|
|
855
|
+
// Default height
|
|
856
|
+
});
|
|
769
857
|
const [isMobile, setIsMobile] = React3.useState(false);
|
|
770
858
|
React3.useEffect(() => {
|
|
771
859
|
const checkMobile = () => {
|
|
@@ -781,6 +869,24 @@ function GeoMap({
|
|
|
781
869
|
}
|
|
782
870
|
return isMobile ? 420 : 520;
|
|
783
871
|
}, [mapSize, isMobile]);
|
|
872
|
+
React3.useEffect(() => {
|
|
873
|
+
if (!containerRef.current) return;
|
|
874
|
+
const updateDimensions = () => {
|
|
875
|
+
if (containerRef.current) {
|
|
876
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
877
|
+
setContainerDimensions({
|
|
878
|
+
width: rect.width || 800,
|
|
879
|
+
height: rect.height || calculatedHeight
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
updateDimensions();
|
|
884
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
885
|
+
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
886
|
+
resizeObserver.observe(containerRef.current);
|
|
887
|
+
return () => resizeObserver.disconnect();
|
|
888
|
+
}
|
|
889
|
+
}, [calculatedHeight]);
|
|
784
890
|
const normalizedStandaloneMarkers = React3.useMemo(
|
|
785
891
|
() => markers.map((marker, index) => ({
|
|
786
892
|
...marker,
|
|
@@ -864,47 +970,49 @@ function GeoMap({
|
|
|
864
970
|
longitude: DEFAULT_VIEW_STATE.longitude
|
|
865
971
|
};
|
|
866
972
|
}, [normalizedClusters, normalizedStandaloneMarkers]);
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
return markerFocusZoom;
|
|
870
|
-
}
|
|
871
|
-
const allCoords = [];
|
|
973
|
+
const zoomCoordinates = React3.useMemo(() => {
|
|
974
|
+
const coords = [];
|
|
872
975
|
normalizedStandaloneMarkers.forEach((marker) => {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
976
|
+
coords.push({
|
|
977
|
+
lat: marker.latitude,
|
|
978
|
+
lng: marker.longitude
|
|
876
979
|
});
|
|
877
980
|
});
|
|
878
981
|
normalizedClusters.forEach((cluster) => {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
982
|
+
coords.push({
|
|
983
|
+
lat: cluster.latitude,
|
|
984
|
+
lng: cluster.longitude
|
|
882
985
|
});
|
|
883
986
|
});
|
|
884
|
-
|
|
987
|
+
return coords;
|
|
988
|
+
}, [normalizedStandaloneMarkers, normalizedClusters]);
|
|
989
|
+
const properZoom = useDefaultZoom({
|
|
990
|
+
coordinates: zoomCoordinates,
|
|
991
|
+
mapWidth: containerDimensions.width,
|
|
992
|
+
mapHeight: containerDimensions.height,
|
|
993
|
+
padding: 80,
|
|
994
|
+
// Increased padding for better framing
|
|
995
|
+
maxZoom: 18,
|
|
996
|
+
minZoom: 1
|
|
997
|
+
});
|
|
998
|
+
const calculatedZoom = React3.useMemo(() => {
|
|
999
|
+
if (zoomCoordinates.length === 1) {
|
|
1000
|
+
return markerFocusZoom;
|
|
1001
|
+
}
|
|
1002
|
+
if (zoomCoordinates.length === 0) {
|
|
885
1003
|
return DEFAULT_VIEW_STATE.zoom;
|
|
886
1004
|
}
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
const lngDiff = Math.max(...lngs) - Math.min(...lngs);
|
|
891
|
-
const maxDiff = Math.max(latDiff, lngDiff);
|
|
892
|
-
const heightFactor = calculatedHeight / 520;
|
|
893
|
-
const paddingFactor = 0.85;
|
|
894
|
-
if (maxDiff > 10) return Math.max(2, 3 * heightFactor * paddingFactor);
|
|
895
|
-
if (maxDiff > 5) return Math.max(4, 5 * heightFactor * paddingFactor);
|
|
896
|
-
if (maxDiff > 2) return Math.max(6, 7 * heightFactor * paddingFactor);
|
|
897
|
-
if (maxDiff > 1) return Math.max(8, 9 * heightFactor * paddingFactor);
|
|
898
|
-
if (maxDiff > 0.5) return Math.max(9, 10 * heightFactor * paddingFactor);
|
|
899
|
-
if (maxDiff > 0.1) return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
900
|
-
if (maxDiff > 0.01) return Math.max(12, 13 * heightFactor * paddingFactor);
|
|
901
|
-
return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
902
|
-
}, [normalizedClusters, normalizedStandaloneMarkers, markerFocusZoom, calculatedHeight]);
|
|
1005
|
+
const adjustedZoom = properZoom ? properZoom - 0.5 : DEFAULT_VIEW_STATE.zoom;
|
|
1006
|
+
return Math.min(adjustedZoom, 15);
|
|
1007
|
+
}, [properZoom, zoomCoordinates.length, markerFocusZoom]);
|
|
903
1008
|
const [uncontrolledViewState, setUncontrolledViewState] = React3.useState({
|
|
904
1009
|
latitude: defaultViewState?.latitude ?? firstCoordinate.latitude,
|
|
905
1010
|
longitude: defaultViewState?.longitude ?? firstCoordinate.longitude,
|
|
906
1011
|
zoom: defaultViewState?.zoom ?? calculatedZoom
|
|
907
1012
|
});
|
|
1013
|
+
const [dynamicPanelPosition, setDynamicPanelPosition] = React3.useState(
|
|
1014
|
+
panelPosition
|
|
1015
|
+
);
|
|
908
1016
|
React3.useEffect(() => {
|
|
909
1017
|
if (!viewState && !defaultViewState) {
|
|
910
1018
|
setUncontrolledViewState({
|
|
@@ -984,6 +1092,19 @@ function GeoMap({
|
|
|
984
1092
|
markerId: marker.id,
|
|
985
1093
|
clusterId: marker.clusterId
|
|
986
1094
|
});
|
|
1095
|
+
const currentCenter = resolvedViewState || {
|
|
1096
|
+
latitude: firstCoordinate.latitude,
|
|
1097
|
+
longitude: firstCoordinate.longitude
|
|
1098
|
+
};
|
|
1099
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1100
|
+
marker.latitude,
|
|
1101
|
+
marker.longitude,
|
|
1102
|
+
{
|
|
1103
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1104
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1105
|
+
}
|
|
1106
|
+
);
|
|
1107
|
+
setDynamicPanelPosition(optimalPosition);
|
|
987
1108
|
applyViewState({
|
|
988
1109
|
latitude: marker.latitude,
|
|
989
1110
|
longitude: marker.longitude,
|
|
@@ -991,7 +1112,7 @@ function GeoMap({
|
|
|
991
1112
|
});
|
|
992
1113
|
emitSelectionChange({ type: "marker", marker });
|
|
993
1114
|
},
|
|
994
|
-
[applyViewState, emitSelectionChange, markerFocusZoom]
|
|
1115
|
+
[applyViewState, emitSelectionChange, markerFocusZoom, resolvedViewState, firstCoordinate]
|
|
995
1116
|
);
|
|
996
1117
|
const selectCluster = React3.useCallback(
|
|
997
1118
|
(cluster) => {
|
|
@@ -999,6 +1120,19 @@ function GeoMap({
|
|
|
999
1120
|
type: "cluster",
|
|
1000
1121
|
clusterId: cluster.id
|
|
1001
1122
|
});
|
|
1123
|
+
const currentCenter = resolvedViewState || {
|
|
1124
|
+
latitude: firstCoordinate.latitude,
|
|
1125
|
+
longitude: firstCoordinate.longitude
|
|
1126
|
+
};
|
|
1127
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1128
|
+
cluster.latitude,
|
|
1129
|
+
cluster.longitude,
|
|
1130
|
+
{
|
|
1131
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1132
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
1135
|
+
setDynamicPanelPosition(optimalPosition);
|
|
1002
1136
|
applyViewState({
|
|
1003
1137
|
latitude: cluster.latitude,
|
|
1004
1138
|
longitude: cluster.longitude,
|
|
@@ -1006,7 +1140,7 @@ function GeoMap({
|
|
|
1006
1140
|
});
|
|
1007
1141
|
emitSelectionChange({ type: "cluster", cluster });
|
|
1008
1142
|
},
|
|
1009
|
-
[applyViewState, clusterFocusZoom, emitSelectionChange]
|
|
1143
|
+
[applyViewState, clusterFocusZoom, emitSelectionChange, resolvedViewState, firstCoordinate]
|
|
1010
1144
|
);
|
|
1011
1145
|
const clearSelection = React3.useCallback(() => {
|
|
1012
1146
|
setSelection({ type: "none" });
|
|
@@ -1225,6 +1359,7 @@ function GeoMap({
|
|
|
1225
1359
|
return /* @__PURE__ */ jsxs(
|
|
1226
1360
|
"div",
|
|
1227
1361
|
{
|
|
1362
|
+
ref: containerRef,
|
|
1228
1363
|
className: cn(
|
|
1229
1364
|
"relative rounded-2xl border border-border bg-background",
|
|
1230
1365
|
// Remove overflow-hidden from outer container to allow panel to overflow
|
|
@@ -1292,7 +1427,7 @@ function GeoMap({
|
|
|
1292
1427
|
{
|
|
1293
1428
|
className: cn(
|
|
1294
1429
|
"pointer-events-none absolute z-30",
|
|
1295
|
-
PANEL_POSITION_CLASS[
|
|
1430
|
+
PANEL_POSITION_CLASS[dynamicPanelPosition]
|
|
1296
1431
|
),
|
|
1297
1432
|
style: {
|
|
1298
1433
|
// Ensure panel can overflow and has higher z-index
|