@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
|
@@ -658,12 +658,93 @@ function MapLibre({
|
|
|
658
658
|
}
|
|
659
659
|
);
|
|
660
660
|
}
|
|
661
|
+
var TILE_SIZE = 512;
|
|
662
|
+
function latToMercatorY(lat) {
|
|
663
|
+
const latRad = lat * Math.PI / 180;
|
|
664
|
+
const mercN = Math.log(Math.tan(Math.PI / 4 + latRad / 2));
|
|
665
|
+
return TILE_SIZE / (2 * Math.PI) * (Math.PI - mercN);
|
|
666
|
+
}
|
|
667
|
+
function lngToMercatorX(lng) {
|
|
668
|
+
return TILE_SIZE / (2 * Math.PI) * ((lng + 180) / 360 * 2 * Math.PI);
|
|
669
|
+
}
|
|
670
|
+
function computeDefaultZoom(options) {
|
|
671
|
+
const {
|
|
672
|
+
coordinates,
|
|
673
|
+
mapWidth,
|
|
674
|
+
mapHeight,
|
|
675
|
+
padding = 50,
|
|
676
|
+
maxZoom = 18,
|
|
677
|
+
minZoom = 1
|
|
678
|
+
} = options;
|
|
679
|
+
if (coordinates.length === 0) return null;
|
|
680
|
+
if (coordinates.length === 1) return maxZoom;
|
|
681
|
+
if (mapWidth <= 0 || mapHeight <= 0) return null;
|
|
682
|
+
let minLat = Infinity;
|
|
683
|
+
let maxLat = -Infinity;
|
|
684
|
+
let minLng = Infinity;
|
|
685
|
+
let maxLng = -Infinity;
|
|
686
|
+
for (const coord of coordinates) {
|
|
687
|
+
if (coord.lat < minLat) minLat = coord.lat;
|
|
688
|
+
if (coord.lat > maxLat) maxLat = coord.lat;
|
|
689
|
+
if (coord.lng < minLng) minLng = coord.lng;
|
|
690
|
+
if (coord.lng > maxLng) maxLng = coord.lng;
|
|
691
|
+
}
|
|
692
|
+
const pixelXMin = lngToMercatorX(minLng);
|
|
693
|
+
const pixelXMax = lngToMercatorX(maxLng);
|
|
694
|
+
const pixelYMin = latToMercatorY(maxLat);
|
|
695
|
+
const pixelYMax = latToMercatorY(minLat);
|
|
696
|
+
const dx = Math.abs(pixelXMax - pixelXMin);
|
|
697
|
+
const dy = Math.abs(pixelYMax - pixelYMin);
|
|
698
|
+
const availableWidth = mapWidth - padding * 2;
|
|
699
|
+
const availableHeight = mapHeight - padding * 2;
|
|
700
|
+
if (availableWidth <= 0 || availableHeight <= 0) return minZoom;
|
|
701
|
+
let zoom;
|
|
702
|
+
if (dx === 0 && dy === 0) {
|
|
703
|
+
return maxZoom;
|
|
704
|
+
} else if (dx === 0) {
|
|
705
|
+
zoom = Math.log2(availableHeight / dy);
|
|
706
|
+
} else if (dy === 0) {
|
|
707
|
+
zoom = Math.log2(availableWidth / dx);
|
|
708
|
+
} else {
|
|
709
|
+
const zoomX = Math.log2(availableWidth / dx);
|
|
710
|
+
const zoomY = Math.log2(availableHeight / dy);
|
|
711
|
+
zoom = Math.min(zoomX, zoomY);
|
|
712
|
+
}
|
|
713
|
+
return Math.max(minZoom, Math.min(maxZoom, Math.floor(zoom * 100) / 100));
|
|
714
|
+
}
|
|
715
|
+
function useDefaultZoom(options) {
|
|
716
|
+
const { coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom } = options;
|
|
717
|
+
return React3.useMemo(
|
|
718
|
+
() => computeDefaultZoom({
|
|
719
|
+
coordinates,
|
|
720
|
+
mapWidth,
|
|
721
|
+
mapHeight,
|
|
722
|
+
padding,
|
|
723
|
+
maxZoom,
|
|
724
|
+
minZoom
|
|
725
|
+
}),
|
|
726
|
+
[coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom]
|
|
727
|
+
);
|
|
728
|
+
}
|
|
661
729
|
var PANEL_POSITION_CLASS = {
|
|
662
730
|
"top-left": "left-4 top-4",
|
|
663
731
|
"top-right": "right-4 top-4",
|
|
664
732
|
"bottom-left": "bottom-4 left-4",
|
|
665
733
|
"bottom-right": "bottom-4 right-4"
|
|
666
734
|
};
|
|
735
|
+
function getOptimalPanelPosition(markerLat, markerLng, mapCenter) {
|
|
736
|
+
const isNorth = markerLat >= mapCenter.latitude;
|
|
737
|
+
const isEast = markerLng >= mapCenter.longitude;
|
|
738
|
+
if (isNorth && isEast) {
|
|
739
|
+
return "bottom-left";
|
|
740
|
+
} else if (isNorth && !isEast) {
|
|
741
|
+
return "bottom-right";
|
|
742
|
+
} else if (!isNorth && isEast) {
|
|
743
|
+
return "top-left";
|
|
744
|
+
} else {
|
|
745
|
+
return "top-right";
|
|
746
|
+
}
|
|
747
|
+
}
|
|
667
748
|
var DEFAULT_VIEW_STATE = {
|
|
668
749
|
latitude: 39.5,
|
|
669
750
|
longitude: -98.35,
|
|
@@ -906,6 +987,13 @@ function GeoMap({
|
|
|
906
987
|
IconComponent = FallbackIcon,
|
|
907
988
|
ImgComponent = FallbackImg
|
|
908
989
|
}) {
|
|
990
|
+
const containerRef = React3__namespace.useRef(null);
|
|
991
|
+
const [containerDimensions, setContainerDimensions] = React3__namespace.useState({
|
|
992
|
+
width: 800,
|
|
993
|
+
// Default width
|
|
994
|
+
height: 520
|
|
995
|
+
// Default height
|
|
996
|
+
});
|
|
909
997
|
const [isMobile, setIsMobile] = React3__namespace.useState(false);
|
|
910
998
|
React3__namespace.useEffect(() => {
|
|
911
999
|
const checkMobile = () => {
|
|
@@ -921,6 +1009,24 @@ function GeoMap({
|
|
|
921
1009
|
}
|
|
922
1010
|
return isMobile ? 420 : 520;
|
|
923
1011
|
}, [mapSize, isMobile]);
|
|
1012
|
+
React3__namespace.useEffect(() => {
|
|
1013
|
+
if (!containerRef.current) return;
|
|
1014
|
+
const updateDimensions = () => {
|
|
1015
|
+
if (containerRef.current) {
|
|
1016
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
1017
|
+
setContainerDimensions({
|
|
1018
|
+
width: rect.width || 800,
|
|
1019
|
+
height: rect.height || calculatedHeight
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
updateDimensions();
|
|
1024
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1025
|
+
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
1026
|
+
resizeObserver.observe(containerRef.current);
|
|
1027
|
+
return () => resizeObserver.disconnect();
|
|
1028
|
+
}
|
|
1029
|
+
}, [calculatedHeight]);
|
|
924
1030
|
const normalizedStandaloneMarkers = React3__namespace.useMemo(
|
|
925
1031
|
() => markers.map((marker, index) => ({
|
|
926
1032
|
...marker,
|
|
@@ -1004,47 +1110,49 @@ function GeoMap({
|
|
|
1004
1110
|
longitude: DEFAULT_VIEW_STATE.longitude
|
|
1005
1111
|
};
|
|
1006
1112
|
}, [normalizedClusters, normalizedStandaloneMarkers]);
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
return markerFocusZoom;
|
|
1010
|
-
}
|
|
1011
|
-
const allCoords = [];
|
|
1113
|
+
const zoomCoordinates = React3__namespace.useMemo(() => {
|
|
1114
|
+
const coords = [];
|
|
1012
1115
|
normalizedStandaloneMarkers.forEach((marker) => {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1116
|
+
coords.push({
|
|
1117
|
+
lat: marker.latitude,
|
|
1118
|
+
lng: marker.longitude
|
|
1016
1119
|
});
|
|
1017
1120
|
});
|
|
1018
1121
|
normalizedClusters.forEach((cluster) => {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1122
|
+
coords.push({
|
|
1123
|
+
lat: cluster.latitude,
|
|
1124
|
+
lng: cluster.longitude
|
|
1022
1125
|
});
|
|
1023
1126
|
});
|
|
1024
|
-
|
|
1127
|
+
return coords;
|
|
1128
|
+
}, [normalizedStandaloneMarkers, normalizedClusters]);
|
|
1129
|
+
const properZoom = useDefaultZoom({
|
|
1130
|
+
coordinates: zoomCoordinates,
|
|
1131
|
+
mapWidth: containerDimensions.width,
|
|
1132
|
+
mapHeight: containerDimensions.height,
|
|
1133
|
+
padding: 80,
|
|
1134
|
+
// Increased padding for better framing
|
|
1135
|
+
maxZoom: 18,
|
|
1136
|
+
minZoom: 1
|
|
1137
|
+
});
|
|
1138
|
+
const calculatedZoom = React3__namespace.useMemo(() => {
|
|
1139
|
+
if (zoomCoordinates.length === 1) {
|
|
1140
|
+
return markerFocusZoom;
|
|
1141
|
+
}
|
|
1142
|
+
if (zoomCoordinates.length === 0) {
|
|
1025
1143
|
return DEFAULT_VIEW_STATE.zoom;
|
|
1026
1144
|
}
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
const lngDiff = Math.max(...lngs) - Math.min(...lngs);
|
|
1031
|
-
const maxDiff = Math.max(latDiff, lngDiff);
|
|
1032
|
-
const heightFactor = calculatedHeight / 520;
|
|
1033
|
-
const paddingFactor = 0.85;
|
|
1034
|
-
if (maxDiff > 10) return Math.max(2, 3 * heightFactor * paddingFactor);
|
|
1035
|
-
if (maxDiff > 5) return Math.max(4, 5 * heightFactor * paddingFactor);
|
|
1036
|
-
if (maxDiff > 2) return Math.max(6, 7 * heightFactor * paddingFactor);
|
|
1037
|
-
if (maxDiff > 1) return Math.max(8, 9 * heightFactor * paddingFactor);
|
|
1038
|
-
if (maxDiff > 0.5) return Math.max(9, 10 * heightFactor * paddingFactor);
|
|
1039
|
-
if (maxDiff > 0.1) return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
1040
|
-
if (maxDiff > 0.01) return Math.max(12, 13 * heightFactor * paddingFactor);
|
|
1041
|
-
return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
1042
|
-
}, [normalizedClusters, normalizedStandaloneMarkers, markerFocusZoom, calculatedHeight]);
|
|
1145
|
+
const adjustedZoom = properZoom ? properZoom - 0.5 : DEFAULT_VIEW_STATE.zoom;
|
|
1146
|
+
return Math.min(adjustedZoom, 15);
|
|
1147
|
+
}, [properZoom, zoomCoordinates.length, markerFocusZoom]);
|
|
1043
1148
|
const [uncontrolledViewState, setUncontrolledViewState] = React3__namespace.useState({
|
|
1044
1149
|
latitude: defaultViewState?.latitude ?? firstCoordinate.latitude,
|
|
1045
1150
|
longitude: defaultViewState?.longitude ?? firstCoordinate.longitude,
|
|
1046
1151
|
zoom: defaultViewState?.zoom ?? calculatedZoom
|
|
1047
1152
|
});
|
|
1153
|
+
const [dynamicPanelPosition, setDynamicPanelPosition] = React3__namespace.useState(
|
|
1154
|
+
panelPosition
|
|
1155
|
+
);
|
|
1048
1156
|
React3__namespace.useEffect(() => {
|
|
1049
1157
|
if (!viewState && !defaultViewState) {
|
|
1050
1158
|
setUncontrolledViewState({
|
|
@@ -1124,6 +1232,19 @@ function GeoMap({
|
|
|
1124
1232
|
markerId: marker.id,
|
|
1125
1233
|
clusterId: marker.clusterId
|
|
1126
1234
|
});
|
|
1235
|
+
const currentCenter = resolvedViewState || {
|
|
1236
|
+
latitude: firstCoordinate.latitude,
|
|
1237
|
+
longitude: firstCoordinate.longitude
|
|
1238
|
+
};
|
|
1239
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1240
|
+
marker.latitude,
|
|
1241
|
+
marker.longitude,
|
|
1242
|
+
{
|
|
1243
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1244
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
setDynamicPanelPosition(optimalPosition);
|
|
1127
1248
|
applyViewState({
|
|
1128
1249
|
latitude: marker.latitude,
|
|
1129
1250
|
longitude: marker.longitude,
|
|
@@ -1131,7 +1252,7 @@ function GeoMap({
|
|
|
1131
1252
|
});
|
|
1132
1253
|
emitSelectionChange({ type: "marker", marker });
|
|
1133
1254
|
},
|
|
1134
|
-
[applyViewState, emitSelectionChange, markerFocusZoom]
|
|
1255
|
+
[applyViewState, emitSelectionChange, markerFocusZoom, resolvedViewState, firstCoordinate]
|
|
1135
1256
|
);
|
|
1136
1257
|
const selectCluster = React3__namespace.useCallback(
|
|
1137
1258
|
(cluster) => {
|
|
@@ -1139,6 +1260,19 @@ function GeoMap({
|
|
|
1139
1260
|
type: "cluster",
|
|
1140
1261
|
clusterId: cluster.id
|
|
1141
1262
|
});
|
|
1263
|
+
const currentCenter = resolvedViewState || {
|
|
1264
|
+
latitude: firstCoordinate.latitude,
|
|
1265
|
+
longitude: firstCoordinate.longitude
|
|
1266
|
+
};
|
|
1267
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1268
|
+
cluster.latitude,
|
|
1269
|
+
cluster.longitude,
|
|
1270
|
+
{
|
|
1271
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1272
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
setDynamicPanelPosition(optimalPosition);
|
|
1142
1276
|
applyViewState({
|
|
1143
1277
|
latitude: cluster.latitude,
|
|
1144
1278
|
longitude: cluster.longitude,
|
|
@@ -1146,7 +1280,7 @@ function GeoMap({
|
|
|
1146
1280
|
});
|
|
1147
1281
|
emitSelectionChange({ type: "cluster", cluster });
|
|
1148
1282
|
},
|
|
1149
|
-
[applyViewState, clusterFocusZoom, emitSelectionChange]
|
|
1283
|
+
[applyViewState, clusterFocusZoom, emitSelectionChange, resolvedViewState, firstCoordinate]
|
|
1150
1284
|
);
|
|
1151
1285
|
const clearSelection = React3__namespace.useCallback(() => {
|
|
1152
1286
|
setSelection({ type: "none" });
|
|
@@ -1365,6 +1499,7 @@ function GeoMap({
|
|
|
1365
1499
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1366
1500
|
"div",
|
|
1367
1501
|
{
|
|
1502
|
+
ref: containerRef,
|
|
1368
1503
|
className: cn(
|
|
1369
1504
|
"relative rounded-2xl border border-border bg-background",
|
|
1370
1505
|
// Remove overflow-hidden from outer container to allow panel to overflow
|
|
@@ -1432,7 +1567,7 @@ function GeoMap({
|
|
|
1432
1567
|
{
|
|
1433
1568
|
className: cn(
|
|
1434
1569
|
"pointer-events-none absolute z-30",
|
|
1435
|
-
PANEL_POSITION_CLASS[
|
|
1570
|
+
PANEL_POSITION_CLASS[dynamicPanelPosition]
|
|
1436
1571
|
),
|
|
1437
1572
|
style: {
|
|
1438
1573
|
// Ensure panel can overflow and has higher z-index
|