Refactor cell selection method
Pre-draw lines during map init and then use feature-state to toggle them. Renables pitch and rotation controls. To avoid bad panning / fitBounds, use easeTo to fix bearing and pitch back to 0 before panning.
This commit is contained in:
parent
641957cfb5
commit
141d9c025d
@ -73,6 +73,7 @@ const Map: React.FC = () => {
|
|||||||
if (!map.current) return;
|
if (!map.current) return;
|
||||||
if (map.current && !map.current.getSource("grid-source")) return;
|
if (map.current && !map.current.getSource("grid-source")) return;
|
||||||
|
|
||||||
|
map.current.removeFeatureState({ source: "grid-source" });
|
||||||
map.current.setFeatureState(
|
map.current.setFeatureState(
|
||||||
{
|
{
|
||||||
source: "grid-source",
|
source: "grid-source",
|
||||||
@ -82,76 +83,50 @@ const Map: React.FC = () => {
|
|||||||
selected: true,
|
selected: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
map.current.removeFeatureState({ source: "selected-cell-source" });
|
||||||
|
map.current.setFeatureState(
|
||||||
|
{
|
||||||
|
source: "selected-cell-source",
|
||||||
|
id: (cell.x + 57) * 100 + 50 - cell.y,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellSelected: true,
|
||||||
|
modSelected: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
requestAnimationFrame(() => map.current && map.current.resize());
|
requestAnimationFrame(() => map.current && map.current.resize());
|
||||||
|
|
||||||
var zoom = map.current.getZoom();
|
const panTo = () => {
|
||||||
var viewportNW = map.current.project([-180, 85.051129]);
|
const zoom = map.current.getZoom();
|
||||||
var cellSize = Math.pow(2, zoom + 2);
|
const viewportNW = map.current.project([-180, 85.051129]);
|
||||||
|
const cellSize = Math.pow(2, zoom + 2);
|
||||||
const x = cell.x + 57;
|
const x = cell.x + 57;
|
||||||
const y = 50 - cell.y;
|
const y = 50 - cell.y;
|
||||||
let nw = map.current.unproject([
|
let nw = map.current.unproject([
|
||||||
x * cellSize + viewportNW.x,
|
x * cellSize + viewportNW.x,
|
||||||
y * cellSize + viewportNW.y,
|
y * cellSize + viewportNW.y,
|
||||||
]);
|
]);
|
||||||
let ne = map.current.unproject([
|
|
||||||
x * cellSize + viewportNW.x + cellSize,
|
|
||||||
y * cellSize + viewportNW.y,
|
|
||||||
]);
|
|
||||||
let se = map.current.unproject([
|
let se = map.current.unproject([
|
||||||
x * cellSize + viewportNW.x + cellSize,
|
x * cellSize + viewportNW.x + cellSize,
|
||||||
y * cellSize + viewportNW.y + cellSize,
|
y * cellSize + viewportNW.y + cellSize,
|
||||||
]);
|
]);
|
||||||
let sw = map.current.unproject([
|
|
||||||
x * cellSize + viewportNW.x,
|
|
||||||
y * cellSize + viewportNW.y + cellSize,
|
|
||||||
]);
|
|
||||||
const selectedCellLines: GeoJSON.FeatureCollection<
|
|
||||||
GeoJSON.Geometry,
|
|
||||||
GeoJSON.GeoJsonProperties
|
|
||||||
> = {
|
|
||||||
type: "FeatureCollection",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
type: "Feature",
|
|
||||||
geometry: {
|
|
||||||
type: "LineString",
|
|
||||||
coordinates: [
|
|
||||||
[nw.lng, nw.lat],
|
|
||||||
[ne.lng, ne.lat],
|
|
||||||
[se.lng, se.lat],
|
|
||||||
[sw.lng, sw.lat],
|
|
||||||
[nw.lng, nw.lat],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
properties: { x: x, y: y },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (map.current.getLayer("selected-cell-layer")) {
|
|
||||||
map.current.removeLayer("selected-cell-layer");
|
|
||||||
}
|
|
||||||
if (map.current.getSource("selected-cell-source")) {
|
|
||||||
map.current.removeSource("selected-cell-source");
|
|
||||||
}
|
|
||||||
map.current.addSource("selected-cell-source", {
|
|
||||||
type: "geojson",
|
|
||||||
data: selectedCellLines,
|
|
||||||
});
|
|
||||||
map.current.addLayer({
|
|
||||||
id: "selected-cell-layer",
|
|
||||||
type: "line",
|
|
||||||
source: "selected-cell-source",
|
|
||||||
paint: {
|
|
||||||
"line-color": "blue",
|
|
||||||
"line-width": 3,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const bounds = map.current.getBounds();
|
const bounds = map.current.getBounds();
|
||||||
if (!bounds.contains(nw) || !bounds.contains(se)) {
|
if (!bounds.contains(nw) || !bounds.contains(se)) {
|
||||||
map.current.panTo(nw);
|
map.current.panTo(nw);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
const bearing = map.current.getBearing();
|
||||||
|
const pitch = map.current.getPitch();
|
||||||
|
// This logic breaks with camera rotation / pitch
|
||||||
|
if (bearing !== 0 || pitch !== 0) {
|
||||||
|
map.current.easeTo({ bearing: 0, pitch: 0, duration: 300 });
|
||||||
|
setTimeout(() => {
|
||||||
|
panTo();
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
panTo();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[map]
|
[map]
|
||||||
);
|
);
|
||||||
@ -161,34 +136,33 @@ const Map: React.FC = () => {
|
|||||||
if (!map.current) return;
|
if (!map.current) return;
|
||||||
if (map.current && !map.current.getSource("grid-source")) return;
|
if (map.current && !map.current.getSource("grid-source")) return;
|
||||||
|
|
||||||
var zoom = map.current.getZoom();
|
map.current.removeFeatureState({ source: "selected-cell-source" });
|
||||||
var viewportNW = map.current.project([-180, 85.051129]);
|
for (let cell of cells) {
|
||||||
var cellSize = Math.pow(2, zoom + 2);
|
map.current.setFeatureState(
|
||||||
|
{
|
||||||
|
source: "selected-cell-source",
|
||||||
|
id: (cell.x + 57) * 100 + 50 - cell.y,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modSelected: true,
|
||||||
|
cellSelected: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const selectedCellsLines: GeoJSON.FeatureCollection<
|
|
||||||
GeoJSON.Geometry,
|
|
||||||
GeoJSON.GeoJsonProperties
|
|
||||||
> = {
|
|
||||||
type: "FeatureCollection",
|
|
||||||
features: [],
|
|
||||||
};
|
|
||||||
let bounds: mapboxgl.LngLatBounds | null = null;
|
let bounds: mapboxgl.LngLatBounds | null = null;
|
||||||
|
const fitBounds = () => {
|
||||||
|
const zoom = map.current.getZoom();
|
||||||
|
const viewportNW = map.current.project([-180, 85.051129]);
|
||||||
|
const cellSize = Math.pow(2, zoom + 2);
|
||||||
|
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
const x = cell.x + 57;
|
const x = cell.x + 57;
|
||||||
const y = 50 - cell.y;
|
const y = 50 - cell.y;
|
||||||
let nw = map.current.unproject([
|
|
||||||
x * cellSize + viewportNW.x,
|
|
||||||
y * cellSize + viewportNW.y,
|
|
||||||
]);
|
|
||||||
let ne = map.current.unproject([
|
let ne = map.current.unproject([
|
||||||
x * cellSize + viewportNW.x + cellSize,
|
x * cellSize + viewportNW.x + cellSize,
|
||||||
y * cellSize + viewportNW.y,
|
y * cellSize + viewportNW.y,
|
||||||
]);
|
]);
|
||||||
let se = map.current.unproject([
|
|
||||||
x * cellSize + viewportNW.x + cellSize,
|
|
||||||
y * cellSize + viewportNW.y + cellSize,
|
|
||||||
]);
|
|
||||||
let sw = map.current.unproject([
|
let sw = map.current.unproject([
|
||||||
x * cellSize + viewportNW.x,
|
x * cellSize + viewportNW.x,
|
||||||
y * cellSize + viewportNW.y + cellSize,
|
y * cellSize + viewportNW.y + cellSize,
|
||||||
@ -198,42 +172,8 @@ const Map: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
bounds = new mapboxgl.LngLatBounds(sw, ne);
|
bounds = new mapboxgl.LngLatBounds(sw, ne);
|
||||||
}
|
}
|
||||||
selectedCellsLines.features.push({
|
|
||||||
type: "Feature",
|
|
||||||
geometry: {
|
|
||||||
type: "LineString",
|
|
||||||
coordinates: [
|
|
||||||
[nw.lng, nw.lat],
|
|
||||||
[ne.lng, ne.lat],
|
|
||||||
[se.lng, se.lat],
|
|
||||||
[sw.lng, sw.lat],
|
|
||||||
[nw.lng, nw.lat],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
properties: { x: x, y: y },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map.current.getLayer("selected-cells-layer")) {
|
|
||||||
map.current.removeLayer("selected-cells-layer");
|
|
||||||
}
|
|
||||||
if (map.current.getSource("selected-cells-source")) {
|
|
||||||
map.current.removeSource("selected-cells-source");
|
|
||||||
}
|
|
||||||
map.current.addSource("selected-cells-source", {
|
|
||||||
type: "geojson",
|
|
||||||
data: selectedCellsLines,
|
|
||||||
});
|
|
||||||
map.current.addLayer({
|
|
||||||
id: "selected-cells-layer",
|
|
||||||
type: "line",
|
|
||||||
source: "selected-cells-source",
|
|
||||||
paint: {
|
|
||||||
"line-color": "purple",
|
|
||||||
"line-width": 4,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (map.current) {
|
if (map.current) {
|
||||||
map.current.resize();
|
map.current.resize();
|
||||||
@ -242,6 +182,19 @@ const Map: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const bearing = map.current.getBearing();
|
||||||
|
const pitch = map.current.getPitch();
|
||||||
|
// This logic breaks with camera rotation / pitch
|
||||||
|
if (bearing !== 0 || pitch !== 0) {
|
||||||
|
map.current.easeTo({ bearing: 0, pitch: 0, duration: 300 });
|
||||||
|
setTimeout(() => {
|
||||||
|
fitBounds();
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
fitBounds();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[map]
|
[map]
|
||||||
);
|
);
|
||||||
@ -258,11 +211,8 @@ const Map: React.FC = () => {
|
|||||||
const clearSelectedCell = useCallback(() => {
|
const clearSelectedCell = useCallback(() => {
|
||||||
setSelectedCell(null);
|
setSelectedCell(null);
|
||||||
if (map.current) map.current.removeFeatureState({ source: "grid-source" });
|
if (map.current) map.current.removeFeatureState({ source: "grid-source" });
|
||||||
if (map.current && map.current.getLayer("selected-cell-layer")) {
|
if (map.current) {
|
||||||
map.current.removeLayer("selected-cell-layer");
|
map.current.removeFeatureState({ source: "selected-cell-source" });
|
||||||
}
|
|
||||||
if (map.current && map.current.getSource("selected-cell-source")) {
|
|
||||||
map.current.removeSource("selected-cell-source");
|
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (map.current) map.current.resize();
|
if (map.current) map.current.resize();
|
||||||
@ -271,11 +221,8 @@ const Map: React.FC = () => {
|
|||||||
|
|
||||||
const clearSelectedCells = useCallback(() => {
|
const clearSelectedCells = useCallback(() => {
|
||||||
setSelectedCells(null);
|
setSelectedCells(null);
|
||||||
if (map.current && map.current.getLayer("selected-cells-layer")) {
|
if (map.current) {
|
||||||
map.current.removeLayer("selected-cells-layer");
|
map.current.removeFeatureState({ source: "selected-cell-source" });
|
||||||
}
|
|
||||||
if (map.current && map.current.getSource("selected-cells-source")) {
|
|
||||||
map.current.removeSource("selected-cells-source");
|
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (map.current) map.current.resize();
|
if (map.current) map.current.resize();
|
||||||
@ -301,39 +248,32 @@ const Map: React.FC = () => {
|
|||||||
selectedCell.x !== cell.x ||
|
selectedCell.x !== cell.x ||
|
||||||
selectedCell.y !== cell.y
|
selectedCell.y !== cell.y
|
||||||
) {
|
) {
|
||||||
|
clearSelectedCells();
|
||||||
selectCell(cell);
|
selectCell(cell);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (
|
||||||
if (selectedCell) {
|
|
||||||
clearSelectedCell();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
selectedCell,
|
|
||||||
router.query.cell,
|
|
||||||
router.query.mod,
|
|
||||||
selectCell,
|
|
||||||
clearSelectedCell,
|
|
||||||
heatmapLoaded,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!heatmapLoaded) return; // wait for all map layers to load
|
|
||||||
if (
|
|
||||||
router.query.mod &&
|
router.query.mod &&
|
||||||
typeof router.query.mod === "string" &&
|
typeof router.query.mod === "string" &&
|
||||||
selectedCells
|
selectedCells
|
||||||
) {
|
) {
|
||||||
|
clearSelectedCell();
|
||||||
selectCells(selectedCells);
|
selectCells(selectedCells);
|
||||||
} else {
|
} else {
|
||||||
|
if (selectedCell) {
|
||||||
|
clearSelectedCell();
|
||||||
|
}
|
||||||
if (selectedCells) {
|
if (selectedCells) {
|
||||||
clearSelectedCells();
|
clearSelectedCells();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
selectedCell,
|
||||||
selectedCells,
|
selectedCells,
|
||||||
|
router.query.cell,
|
||||||
router.query.mod,
|
router.query.mod,
|
||||||
|
selectCell,
|
||||||
selectCells,
|
selectCells,
|
||||||
|
clearSelectedCell,
|
||||||
clearSelectedCells,
|
clearSelectedCells,
|
||||||
heatmapLoaded,
|
heatmapLoaded,
|
||||||
]);
|
]);
|
||||||
@ -377,8 +317,6 @@ const Map: React.FC = () => {
|
|||||||
[-180, -85.051129],
|
[-180, -85.051129],
|
||||||
[180, 85.051129],
|
[180, 85.051129],
|
||||||
],
|
],
|
||||||
dragRotate: false,
|
|
||||||
pitchWithRotate: false,
|
|
||||||
});
|
});
|
||||||
map.current.on("load", () => {
|
map.current.on("load", () => {
|
||||||
setMapLoaded(true);
|
setMapLoaded(true);
|
||||||
@ -580,20 +518,84 @@ const Map: React.FC = () => {
|
|||||||
},
|
},
|
||||||
"grid-labels-layer"
|
"grid-labels-layer"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedCellLines: GeoJSON.FeatureCollection<
|
||||||
|
GeoJSON.Geometry,
|
||||||
|
GeoJSON.GeoJsonProperties
|
||||||
|
> = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features: [],
|
||||||
|
};
|
||||||
|
for (let x = 0; x < 128; x += 1) {
|
||||||
|
for (let y = 0; y < 128; y += 1) {
|
||||||
|
let nw = map.current.unproject([
|
||||||
|
x * cellSize + viewportNW.x,
|
||||||
|
y * cellSize + viewportNW.y,
|
||||||
|
]);
|
||||||
|
let ne = map.current.unproject([
|
||||||
|
x * cellSize + viewportNW.x + cellSize,
|
||||||
|
y * cellSize + viewportNW.y,
|
||||||
|
]);
|
||||||
|
let se = map.current.unproject([
|
||||||
|
x * cellSize + viewportNW.x + cellSize,
|
||||||
|
y * cellSize + viewportNW.y + cellSize,
|
||||||
|
]);
|
||||||
|
let sw = map.current.unproject([
|
||||||
|
x * cellSize + viewportNW.x,
|
||||||
|
y * cellSize + viewportNW.y + cellSize,
|
||||||
|
]);
|
||||||
|
selectedCellLines.features.push({
|
||||||
|
type: "Feature",
|
||||||
|
id: x * 100 + y,
|
||||||
|
geometry: {
|
||||||
|
type: "LineString",
|
||||||
|
coordinates: [
|
||||||
|
[nw.lng, nw.lat],
|
||||||
|
[ne.lng, ne.lat],
|
||||||
|
[se.lng, se.lat],
|
||||||
|
[sw.lng, sw.lat],
|
||||||
|
[nw.lng, nw.lat],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
properties: { x: x, y: y },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.current.addSource("selected-cell-source", {
|
||||||
|
type: "geojson",
|
||||||
|
data: selectedCellLines,
|
||||||
|
});
|
||||||
|
map.current.addLayer({
|
||||||
|
id: "selected-cell-layer",
|
||||||
|
type: "line",
|
||||||
|
source: "selected-cell-source",
|
||||||
|
paint: {
|
||||||
|
"line-color": [
|
||||||
|
"case",
|
||||||
|
["boolean", ["feature-state", "cellSelected"], false],
|
||||||
|
"blue",
|
||||||
|
["boolean", ["feature-state", "modSelected"], false],
|
||||||
|
"purple",
|
||||||
|
"transparent",
|
||||||
|
],
|
||||||
|
"line-width": [
|
||||||
|
"case",
|
||||||
|
["boolean", ["feature-state", "modSelected"], false],
|
||||||
|
4,
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const fullscreenControl = new mapboxgl.FullscreenControl();
|
const fullscreenControl = new mapboxgl.FullscreenControl();
|
||||||
(fullscreenControl as unknown as { _container: HTMLElement })._container =
|
(fullscreenControl as unknown as { _container: HTMLElement })._container =
|
||||||
mapWrapper.current;
|
mapWrapper.current;
|
||||||
map.current.addControl(fullscreenControl);
|
map.current.addControl(fullscreenControl);
|
||||||
map.current.addControl(
|
map.current.addControl(new mapboxgl.NavigationControl());
|
||||||
new mapboxgl.NavigationControl({ showCompass: false })
|
|
||||||
);
|
|
||||||
|
|
||||||
let singleClickTimeout: NodeJS.Timeout | null = null;
|
|
||||||
map.current.on("click", "grid-layer", (e) => {
|
map.current.on("click", "grid-layer", (e) => {
|
||||||
const features = e.features;
|
const features = e.features;
|
||||||
if (singleClickTimeout) return;
|
|
||||||
singleClickTimeout = setTimeout(() => {
|
|
||||||
singleClickTimeout = null;
|
|
||||||
if (features && features[0]) {
|
if (features && features[0]) {
|
||||||
const cell = {
|
const cell = {
|
||||||
x: features[0].properties!.cellX,
|
x: features[0].properties!.cellX,
|
||||||
@ -601,12 +603,6 @@ const Map: React.FC = () => {
|
|||||||
};
|
};
|
||||||
router.push({ query: { cell: cell.x + "," + cell.y } });
|
router.push({ query: { cell: cell.x + "," + cell.y } });
|
||||||
}
|
}
|
||||||
}, 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
map.current.on("dblclick", "grid-layer", (e) => {
|
|
||||||
if (singleClickTimeout) clearTimeout(singleClickTimeout);
|
|
||||||
singleClickTimeout = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setHeatmapLoaded(true);
|
setHeatmapLoaded(true);
|
||||||
|
Loading…
Reference in New Issue
Block a user