@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
|
@@ -539,12 +539,93 @@ function MapLibre({
|
|
|
539
539
|
}
|
|
540
540
|
);
|
|
541
541
|
}
|
|
542
|
+
var TILE_SIZE = 512;
|
|
543
|
+
function latToMercatorY(lat) {
|
|
544
|
+
const latRad = lat * Math.PI / 180;
|
|
545
|
+
const mercN = Math.log(Math.tan(Math.PI / 4 + latRad / 2));
|
|
546
|
+
return TILE_SIZE / (2 * Math.PI) * (Math.PI - mercN);
|
|
547
|
+
}
|
|
548
|
+
function lngToMercatorX(lng) {
|
|
549
|
+
return TILE_SIZE / (2 * Math.PI) * ((lng + 180) / 360 * 2 * Math.PI);
|
|
550
|
+
}
|
|
551
|
+
function computeDefaultZoom(options) {
|
|
552
|
+
const {
|
|
553
|
+
coordinates,
|
|
554
|
+
mapWidth,
|
|
555
|
+
mapHeight,
|
|
556
|
+
padding = 50,
|
|
557
|
+
maxZoom = 18,
|
|
558
|
+
minZoom = 1
|
|
559
|
+
} = options;
|
|
560
|
+
if (coordinates.length === 0) return null;
|
|
561
|
+
if (coordinates.length === 1) return maxZoom;
|
|
562
|
+
if (mapWidth <= 0 || mapHeight <= 0) return null;
|
|
563
|
+
let minLat = Infinity;
|
|
564
|
+
let maxLat = -Infinity;
|
|
565
|
+
let minLng = Infinity;
|
|
566
|
+
let maxLng = -Infinity;
|
|
567
|
+
for (const coord of coordinates) {
|
|
568
|
+
if (coord.lat < minLat) minLat = coord.lat;
|
|
569
|
+
if (coord.lat > maxLat) maxLat = coord.lat;
|
|
570
|
+
if (coord.lng < minLng) minLng = coord.lng;
|
|
571
|
+
if (coord.lng > maxLng) maxLng = coord.lng;
|
|
572
|
+
}
|
|
573
|
+
const pixelXMin = lngToMercatorX(minLng);
|
|
574
|
+
const pixelXMax = lngToMercatorX(maxLng);
|
|
575
|
+
const pixelYMin = latToMercatorY(maxLat);
|
|
576
|
+
const pixelYMax = latToMercatorY(minLat);
|
|
577
|
+
const dx = Math.abs(pixelXMax - pixelXMin);
|
|
578
|
+
const dy = Math.abs(pixelYMax - pixelYMin);
|
|
579
|
+
const availableWidth = mapWidth - padding * 2;
|
|
580
|
+
const availableHeight = mapHeight - padding * 2;
|
|
581
|
+
if (availableWidth <= 0 || availableHeight <= 0) return minZoom;
|
|
582
|
+
let zoom;
|
|
583
|
+
if (dx === 0 && dy === 0) {
|
|
584
|
+
return maxZoom;
|
|
585
|
+
} else if (dx === 0) {
|
|
586
|
+
zoom = Math.log2(availableHeight / dy);
|
|
587
|
+
} else if (dy === 0) {
|
|
588
|
+
zoom = Math.log2(availableWidth / dx);
|
|
589
|
+
} else {
|
|
590
|
+
const zoomX = Math.log2(availableWidth / dx);
|
|
591
|
+
const zoomY = Math.log2(availableHeight / dy);
|
|
592
|
+
zoom = Math.min(zoomX, zoomY);
|
|
593
|
+
}
|
|
594
|
+
return Math.max(minZoom, Math.min(maxZoom, Math.floor(zoom * 100) / 100));
|
|
595
|
+
}
|
|
596
|
+
function useDefaultZoom(options) {
|
|
597
|
+
const { coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom } = options;
|
|
598
|
+
return React3.useMemo(
|
|
599
|
+
() => computeDefaultZoom({
|
|
600
|
+
coordinates,
|
|
601
|
+
mapWidth,
|
|
602
|
+
mapHeight,
|
|
603
|
+
padding,
|
|
604
|
+
maxZoom,
|
|
605
|
+
minZoom
|
|
606
|
+
}),
|
|
607
|
+
[coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom]
|
|
608
|
+
);
|
|
609
|
+
}
|
|
542
610
|
var PANEL_POSITION_CLASS = {
|
|
543
611
|
"top-left": "left-4 top-4",
|
|
544
612
|
"top-right": "right-4 top-4",
|
|
545
613
|
"bottom-left": "bottom-4 left-4",
|
|
546
614
|
"bottom-right": "bottom-4 right-4"
|
|
547
615
|
};
|
|
616
|
+
function getOptimalPanelPosition(markerLat, markerLng, mapCenter) {
|
|
617
|
+
const isNorth = markerLat >= mapCenter.latitude;
|
|
618
|
+
const isEast = markerLng >= mapCenter.longitude;
|
|
619
|
+
if (isNorth && isEast) {
|
|
620
|
+
return "bottom-left";
|
|
621
|
+
} else if (isNorth && !isEast) {
|
|
622
|
+
return "bottom-right";
|
|
623
|
+
} else if (!isNorth && isEast) {
|
|
624
|
+
return "top-left";
|
|
625
|
+
} else {
|
|
626
|
+
return "top-right";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
548
629
|
var DEFAULT_VIEW_STATE = {
|
|
549
630
|
latitude: 39.5,
|
|
550
631
|
longitude: -98.35,
|
|
@@ -787,6 +868,13 @@ function GeoMap({
|
|
|
787
868
|
IconComponent = FallbackIcon,
|
|
788
869
|
ImgComponent = FallbackImg
|
|
789
870
|
}) {
|
|
871
|
+
const containerRef = React3__namespace.useRef(null);
|
|
872
|
+
const [containerDimensions, setContainerDimensions] = React3__namespace.useState({
|
|
873
|
+
width: 800,
|
|
874
|
+
// Default width
|
|
875
|
+
height: 520
|
|
876
|
+
// Default height
|
|
877
|
+
});
|
|
790
878
|
const [isMobile, setIsMobile] = React3__namespace.useState(false);
|
|
791
879
|
React3__namespace.useEffect(() => {
|
|
792
880
|
const checkMobile = () => {
|
|
@@ -802,6 +890,24 @@ function GeoMap({
|
|
|
802
890
|
}
|
|
803
891
|
return isMobile ? 420 : 520;
|
|
804
892
|
}, [mapSize, isMobile]);
|
|
893
|
+
React3__namespace.useEffect(() => {
|
|
894
|
+
if (!containerRef.current) return;
|
|
895
|
+
const updateDimensions = () => {
|
|
896
|
+
if (containerRef.current) {
|
|
897
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
898
|
+
setContainerDimensions({
|
|
899
|
+
width: rect.width || 800,
|
|
900
|
+
height: rect.height || calculatedHeight
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
updateDimensions();
|
|
905
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
906
|
+
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
907
|
+
resizeObserver.observe(containerRef.current);
|
|
908
|
+
return () => resizeObserver.disconnect();
|
|
909
|
+
}
|
|
910
|
+
}, [calculatedHeight]);
|
|
805
911
|
const normalizedStandaloneMarkers = React3__namespace.useMemo(
|
|
806
912
|
() => markers.map((marker, index) => ({
|
|
807
913
|
...marker,
|
|
@@ -885,47 +991,49 @@ function GeoMap({
|
|
|
885
991
|
longitude: DEFAULT_VIEW_STATE.longitude
|
|
886
992
|
};
|
|
887
993
|
}, [normalizedClusters, normalizedStandaloneMarkers]);
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
return markerFocusZoom;
|
|
891
|
-
}
|
|
892
|
-
const allCoords = [];
|
|
994
|
+
const zoomCoordinates = React3__namespace.useMemo(() => {
|
|
995
|
+
const coords = [];
|
|
893
996
|
normalizedStandaloneMarkers.forEach((marker) => {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
997
|
+
coords.push({
|
|
998
|
+
lat: marker.latitude,
|
|
999
|
+
lng: marker.longitude
|
|
897
1000
|
});
|
|
898
1001
|
});
|
|
899
1002
|
normalizedClusters.forEach((cluster) => {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1003
|
+
coords.push({
|
|
1004
|
+
lat: cluster.latitude,
|
|
1005
|
+
lng: cluster.longitude
|
|
903
1006
|
});
|
|
904
1007
|
});
|
|
905
|
-
|
|
1008
|
+
return coords;
|
|
1009
|
+
}, [normalizedStandaloneMarkers, normalizedClusters]);
|
|
1010
|
+
const properZoom = useDefaultZoom({
|
|
1011
|
+
coordinates: zoomCoordinates,
|
|
1012
|
+
mapWidth: containerDimensions.width,
|
|
1013
|
+
mapHeight: containerDimensions.height,
|
|
1014
|
+
padding: 80,
|
|
1015
|
+
// Increased padding for better framing
|
|
1016
|
+
maxZoom: 18,
|
|
1017
|
+
minZoom: 1
|
|
1018
|
+
});
|
|
1019
|
+
const calculatedZoom = React3__namespace.useMemo(() => {
|
|
1020
|
+
if (zoomCoordinates.length === 1) {
|
|
1021
|
+
return markerFocusZoom;
|
|
1022
|
+
}
|
|
1023
|
+
if (zoomCoordinates.length === 0) {
|
|
906
1024
|
return DEFAULT_VIEW_STATE.zoom;
|
|
907
1025
|
}
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const lngDiff = Math.max(...lngs) - Math.min(...lngs);
|
|
912
|
-
const maxDiff = Math.max(latDiff, lngDiff);
|
|
913
|
-
const heightFactor = calculatedHeight / 520;
|
|
914
|
-
const paddingFactor = 0.85;
|
|
915
|
-
if (maxDiff > 10) return Math.max(2, 3 * heightFactor * paddingFactor);
|
|
916
|
-
if (maxDiff > 5) return Math.max(4, 5 * heightFactor * paddingFactor);
|
|
917
|
-
if (maxDiff > 2) return Math.max(6, 7 * heightFactor * paddingFactor);
|
|
918
|
-
if (maxDiff > 1) return Math.max(8, 9 * heightFactor * paddingFactor);
|
|
919
|
-
if (maxDiff > 0.5) return Math.max(9, 10 * heightFactor * paddingFactor);
|
|
920
|
-
if (maxDiff > 0.1) return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
921
|
-
if (maxDiff > 0.01) return Math.max(12, 13 * heightFactor * paddingFactor);
|
|
922
|
-
return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
923
|
-
}, [normalizedClusters, normalizedStandaloneMarkers, markerFocusZoom, calculatedHeight]);
|
|
1026
|
+
const adjustedZoom = properZoom ? properZoom - 0.5 : DEFAULT_VIEW_STATE.zoom;
|
|
1027
|
+
return Math.min(adjustedZoom, 15);
|
|
1028
|
+
}, [properZoom, zoomCoordinates.length, markerFocusZoom]);
|
|
924
1029
|
const [uncontrolledViewState, setUncontrolledViewState] = React3__namespace.useState({
|
|
925
1030
|
latitude: defaultViewState?.latitude ?? firstCoordinate.latitude,
|
|
926
1031
|
longitude: defaultViewState?.longitude ?? firstCoordinate.longitude,
|
|
927
1032
|
zoom: defaultViewState?.zoom ?? calculatedZoom
|
|
928
1033
|
});
|
|
1034
|
+
const [dynamicPanelPosition, setDynamicPanelPosition] = React3__namespace.useState(
|
|
1035
|
+
panelPosition
|
|
1036
|
+
);
|
|
929
1037
|
React3__namespace.useEffect(() => {
|
|
930
1038
|
if (!viewState && !defaultViewState) {
|
|
931
1039
|
setUncontrolledViewState({
|
|
@@ -1005,6 +1113,19 @@ function GeoMap({
|
|
|
1005
1113
|
markerId: marker.id,
|
|
1006
1114
|
clusterId: marker.clusterId
|
|
1007
1115
|
});
|
|
1116
|
+
const currentCenter = resolvedViewState || {
|
|
1117
|
+
latitude: firstCoordinate.latitude,
|
|
1118
|
+
longitude: firstCoordinate.longitude
|
|
1119
|
+
};
|
|
1120
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1121
|
+
marker.latitude,
|
|
1122
|
+
marker.longitude,
|
|
1123
|
+
{
|
|
1124
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1125
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
setDynamicPanelPosition(optimalPosition);
|
|
1008
1129
|
applyViewState({
|
|
1009
1130
|
latitude: marker.latitude,
|
|
1010
1131
|
longitude: marker.longitude,
|
|
@@ -1012,7 +1133,7 @@ function GeoMap({
|
|
|
1012
1133
|
});
|
|
1013
1134
|
emitSelectionChange({ type: "marker", marker });
|
|
1014
1135
|
},
|
|
1015
|
-
[applyViewState, emitSelectionChange, markerFocusZoom]
|
|
1136
|
+
[applyViewState, emitSelectionChange, markerFocusZoom, resolvedViewState, firstCoordinate]
|
|
1016
1137
|
);
|
|
1017
1138
|
const selectCluster = React3__namespace.useCallback(
|
|
1018
1139
|
(cluster) => {
|
|
@@ -1020,6 +1141,19 @@ function GeoMap({
|
|
|
1020
1141
|
type: "cluster",
|
|
1021
1142
|
clusterId: cluster.id
|
|
1022
1143
|
});
|
|
1144
|
+
const currentCenter = resolvedViewState || {
|
|
1145
|
+
latitude: firstCoordinate.latitude,
|
|
1146
|
+
longitude: firstCoordinate.longitude
|
|
1147
|
+
};
|
|
1148
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1149
|
+
cluster.latitude,
|
|
1150
|
+
cluster.longitude,
|
|
1151
|
+
{
|
|
1152
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1153
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1154
|
+
}
|
|
1155
|
+
);
|
|
1156
|
+
setDynamicPanelPosition(optimalPosition);
|
|
1023
1157
|
applyViewState({
|
|
1024
1158
|
latitude: cluster.latitude,
|
|
1025
1159
|
longitude: cluster.longitude,
|
|
@@ -1027,7 +1161,7 @@ function GeoMap({
|
|
|
1027
1161
|
});
|
|
1028
1162
|
emitSelectionChange({ type: "cluster", cluster });
|
|
1029
1163
|
},
|
|
1030
|
-
[applyViewState, clusterFocusZoom, emitSelectionChange]
|
|
1164
|
+
[applyViewState, clusterFocusZoom, emitSelectionChange, resolvedViewState, firstCoordinate]
|
|
1031
1165
|
);
|
|
1032
1166
|
const clearSelection = React3__namespace.useCallback(() => {
|
|
1033
1167
|
setSelection({ type: "none" });
|
|
@@ -1246,6 +1380,7 @@ function GeoMap({
|
|
|
1246
1380
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1247
1381
|
"div",
|
|
1248
1382
|
{
|
|
1383
|
+
ref: containerRef,
|
|
1249
1384
|
className: cn(
|
|
1250
1385
|
"relative rounded-2xl border border-border bg-background",
|
|
1251
1386
|
// Remove overflow-hidden from outer container to allow panel to overflow
|
|
@@ -1313,7 +1448,7 @@ function GeoMap({
|
|
|
1313
1448
|
{
|
|
1314
1449
|
className: cn(
|
|
1315
1450
|
"pointer-events-none absolute z-30",
|
|
1316
|
-
PANEL_POSITION_CLASS[
|
|
1451
|
+
PANEL_POSITION_CLASS[dynamicPanelPosition]
|
|
1317
1452
|
),
|
|
1318
1453
|
style: {
|
|
1319
1454
|
// Ensure panel can overflow and has higher z-index
|