Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Poi icon scaling #178

Merged
merged 2 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 69 additions & 12 deletions Website/src/app/components/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import { PointOfInterest, POIType, POITypeIconValues } from "@/utils/api";
import { POIIconImg } from "@/utils/common";
import TrackerCharge from "@/app/components/tracker";

/**
* The side lengths of the poi icons in rem
*/
const POI_ICON_SIZES = {
tiny: 0.5,
small: 1,
medium: 2,
large: 3,
xl: 4
} as const;

/**
* Constructs the content of the popup for a POI, without React
* @param poi The POI to construct the popup for
Expand Down Expand Up @@ -50,17 +61,28 @@ function Map({

// We also need state for the center of the map, the vehicle in focus and the container containing the contents of an open popup
const [position, setPosition] = useState(initial_position);
const [zoomLevel, setZoomLevel] = useState(initial_zoom_level);
const [popupContainer, setPopupContainer] = useState(undefined as undefined | HTMLDivElement);

// find the vehicle that is in focus, but only if either the vehicles, or the focus changes.
const vehicleInFocus = useMemo(() => vehicles.find(v => v.id == focus), [vehicles, focus]);

// derive the appropriate POI Icon size from the zoom level. These are arbitrarily chosen values that seemed right to me
const poiIconSize: keyof typeof POI_ICON_SIZES =
zoomLevel < 8 ? "tiny" : zoomLevel < 12 ? "small" : zoomLevel < 14 ? "medium" : zoomLevel < 16 ? "large" : "xl";

const poiIconSideLength = POI_ICON_SIZES[poiIconSize];

// create icons for each poi type
const enriched_poi_types: (POIType & { leaf_icon: L.Icon })[] = useMemo(
() =>
poi_types.map(pt => {
const icon_src = POIIconImg[pt.icon] ?? POIIconImg[POITypeIconValues.Generic];
const leaf_icon = L.icon({ iconUrl: icon_src, iconSize: [45, 45] });

// set an initial icon size, will be modified in via css
const iconSize: [number, number] = [45, 45];

const leaf_icon = L.icon({ iconUrl: icon_src, iconSize, className: "poi-icon transition-all" });

return {
...pt,
Expand All @@ -70,8 +92,6 @@ function Map({
[poi_types]
);

// debugger;

/** handling the initialization of leaflet. MUST NOT be called twice. */
function insertMap() {
assert(mapContainerRef.current, "Error: Ref to Map Container not populated");
Expand All @@ -91,21 +111,42 @@ function Map({
poiPane.style.zIndex = "550";
poiPane.classList.add("leaflet-marker-pane");
// as POIs don't have shadows, we don't need a poiShadowPane.
}

/**
* Add appropriate event listeners to the map
*/
function addMapEvents() {
const map = mapRef.current;
assert(map != undefined, "Error: Map not ready!");

/*const openrailwaymap = L.tileLayer('http://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
{
attribution: '<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a>, Style: <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA 2.0</a> <a href="http://www.openrailwaymap.org/">OpenRailwayMap</a> and OpenStreetMap',
minZoom: 2,
maxZoom: 19,
tileSize: 256
}).addTo(mapRef.current);*/
map.addEventListener("moveend", () => {
// prevent infinite loops by checking that the map actually moved
const newPos = map.getCenter();
setPosition(oldPos => {
if (newPos.lng !== oldPos.lng || newPos.lat !== oldPos.lat) {
return {
lat: newPos.lat,
lng: newPos.lng
};
}
return oldPos;
});
});

map.addEventListener("zoomend", () => {
// React can automatically debounce this, as zoom level is just a number.
const newZoomLevel = map.getZoom();

setZoomLevel(newZoomLevel);
});
}

/** Set the zoom level of the map */
function setMapZoom() {
assert(mapRef.current != undefined, "Error: Map not ready!");

mapRef.current.setZoom(initial_zoom_level);
mapRef.current.setZoom(zoomLevel);
}

/** Set the center of the map. The zoom level MUST be set before, otherwise leaflet will crash. */
Expand Down Expand Up @@ -239,12 +280,28 @@ function Map({

// Schedule various effects (JS run after the page is rendered) for changes to various state variables.
useEffect(insertMap, []);
useEffect(setMapZoom, [initial_zoom_level]);
useEffect(addMapEvents, [setPosition, setZoomLevel]);
useEffect(setMapZoom, [zoomLevel]);
useEffect(setMapPosition, [position]);
useEffect(addTrackPath, [track_data?.path, track_data]);
useEffect(updateMarkers, [focus, setFocus, vehicles]);
useEffect(addPOIs, [points_of_interest, enriched_poi_types]);

// set the width and height of all poi icons using an effect to prevent re-rendering the icons
useEffect(() => {
// Iterate over all poi icons currently present
for (const poiIcon of document.querySelectorAll(".poi-icon")) {
if (poiIcon instanceof HTMLElement) {
// set the height and width using inline styles.
// this will probably make this component much more fragile than it needs to be...
poiIcon.style.width = poiIcon.style.height = `${poiIconSideLength}rem`;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React does not recommend to dynamically inject a style-tag as this could impact performance. Therefore this is using inline-styles.


// we also need to adjust the margins, so that the icons remain centered
poiIcon.style.marginLeft = poiIcon.style.marginTop = `${-poiIconSideLength / 2}rem`;
}
}
}, [points_of_interest, enriched_poi_types, poiIconSideLength]);

return (
<>
<div id="map" className="absolute inset-0" ref={mapContainerRef} />
Expand Down
5 changes: 2 additions & 3 deletions Website/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { LatLngExpression } from "leaflet";
import { FullTrack, PointOfInterest, POIType, Vehicle } from "./api";
import { FullTrack, PointOfInterest, POIType, Position, Vehicle } from "./api";
import { Dispatch, JSX, SetStateAction } from "react";

export interface MapConfig {
initial_position: LatLngExpression;
initial_position: Position;
initial_zoom_level: number;
vehicles: Vehicle[];
track_data?: FullTrack;
Expand Down
Loading