Skip to content

fix(transitive): render route label background in webgl #817

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

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
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
"distance": 8815.19612218932,
"pathway": false,
"mode": "TRAM",
"route": "MAX Green Line",
"route": "lengthened to show support for long names",
"agencyName": "TriMet",
"agencyUrl": "http://trimet.org/",
"agencyTimeZoneOffset": -28800000,
Expand Down
Binary file added packages/transitive-overlay/src/images/01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/05.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/06.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/07.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/08.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/09.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/15.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/17.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/transitive-overlay/src/images/square.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 125 additions & 14 deletions packages/transitive-overlay/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,58 @@ import bbox from "@turf/bbox";
import { getRouteLayerLayout, patternToRouteFeature } from "./route-layers";
import { drawArc, getFromToAnchors, itineraryToTransitive } from "./util";
import routeArrow from "./images/route_arrow.png";
import capsule1 from "./images/01.png";
Copy link
Collaborator

Choose a reason for hiding this comment

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

For subsequent PR: The images are being downloaded all, even though only one/a subset is actually used.

import capsule3 from "./images/03.png";
import capsule4 from "./images/04.png";
import capsule5 from "./images/05.png";
import capsule6 from "./images/06.png";
import capsule7 from "./images/07.png";
import capsule8 from "./images/08.png";
import capsule9 from "./images/09.png";
import capsule10 from "./images/10.png";
import capsule11 from "./images/11.png";
import capsule12 from "./images/12.png";
import capsule13 from "./images/13.png";
import capsule14 from "./images/14.png";
import capsule15 from "./images/15.png";
import capsule16 from "./images/16.png";
import capsule17 from "./images/17.png";
import rectangle from "./images/square.png";

const CAPSULES = {
3: capsule3,
4: capsule4,
5: capsule5,
6: capsule6,
7: capsule7,
8: capsule8,
9: capsule9,
10: capsule10,
11: capsule11,
12: capsule12,
13: capsule13,
14: capsule14,
15: capsule15,
16: capsule16,
17: capsule17
};

// These are based on the sprites in the image folder
const WIDTH_IMAGE_SIZES = {
6: 1283,
7: 1450,
8: 1617,
9: 1783,
10: 1950,
11: 2117,
12: 2283,
13: 2450,
14: 2617,
15: 2783,
16: 2950,
17: 3117
};
const HEIGHT_IMAGE_SIZE = 533;

export { itineraryToTransitive };

Expand Down Expand Up @@ -97,6 +149,7 @@ type Props = {
type MapImage = {
id: string;
url: string;
options: { sdf?: boolean; content?: [number, number, number, number] };
};

const loadImages = (map: MapRef, images: MapImage[]) => {
Expand All @@ -108,7 +161,7 @@ const loadImages = (map: MapRef, images: MapImage[]) => {
return;
}
if (!map.hasImage(img.id)) {
map.addImage(img.id, image, { sdf: true });
map.addImage(img.id, image, img.options);
}
});
});
Expand All @@ -129,10 +182,79 @@ const TransitiveCanvasOverlay = ({
if (showRouteArrows) {
mapImages.push({
id: "arrow-icon",
url: routeArrow
url: routeArrow,
options: { sdf: true }
});
}

function generateCapsulePadding(
width: number
): [number, number, number, number] {
// Low widths have no padding
if (!WIDTH_IMAGE_SIZES[width]) {
return undefined;
}

// This could be more efficient, but this makes it very clear what is happening.
// Higher widths require more padding
let topPad = 0;
if (width === 6) {
topPad = 100;
}

if (width > 6) {
topPad = 150;
}

if (width > 12) {
topPad = 175;
}

// Each image has same height
return [topPad, 0, WIDTH_IMAGE_SIZES[width] - topPad, HEIGHT_IMAGE_SIZE];
}

mapImages.push({
id: "1",
url: capsule1,
options: {
// These paddings are specifically set so that the circle appears circular
// despite non-circular padding
content: [0, 15, 500, 485],
sdf: true
}
});
mapImages.push({
id: "2",
url: capsule1,
options: {
// These paddings are specifically set so that the circle appears circular
// despite non-circular padding
content: [0, 40, 500, 460],
sdf: true
}
});

// Generate each capsule image from 3 - 17
for (let i = 3; i < 18; i++) {
mapImages.push({
id: `${i}`,
url: CAPSULES[i],
options: {
content: generateCapsulePadding(i),
sdf: true
}
});
}

mapImages.push({
id: "rect",
url: rectangle,
options: {
sdf: true
}
});

useEffect(() => {
loadImages(map, mapImages);
}, [map, mapImages]);
Expand Down Expand Up @@ -373,24 +495,13 @@ const TransitiveCanvasOverlay = ({
paint={defaultTextPaintParams}
type="symbol"
/>
<Layer
// Render a solid background of fixed height using the uppercase route name.
filter={routeFilter}
id="routes-labels-background"
layout={getRouteLayerLayout("nameUpper")}
paint={{
"text-color": ["get", "color"],
"text-halo-color": ["get", "color"],
"text-halo-width": 4 // Max value is 1/4 of text size per maplibre docs.
}}
type="symbol"
/>
<Layer
// This layer renders transit route names (foreground).
filter={routeFilter}
id="routes-labels"
layout={getRouteLayerLayout("name")}
paint={{
"icon-color": ["get", "color"],
"text-color": ["get", "textColor"]
}}
type="symbol"
Expand Down
38 changes: 27 additions & 11 deletions packages/transitive-overlay/src/route-layers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import polyline from "@mapbox/polyline";
import { SymbolLayout } from "mapbox-gl";
import { Expression, SymbolLayout } from "mapbox-gl";
import { TransitivePattern, TransitiveRoute } from "@opentripplanner/types";

import { drawArc } from "./util";
Expand All @@ -24,20 +24,10 @@ export function patternToRouteFeature(
return result.concat(coords);
}, []);
const routeName = route.route_short_name || route.route_long_name || "";
// HACK: Create an uppercase version of the route name to paint the background, where
// - spaces are replaced with '!' (~same width as space)
// - "+", "-", certain letters and numbers are replaced with "E" to create a background with a uniform height and fill.
// Also, ensure there is a minimum background width (3 characters).
// Disclaimer: height of substitution characters can vary from font to font.
const routeNameUpper = (routeName.length < 3 ? "EEE" : routeName)
.toUpperCase()
.replace(/\s/g, "!")
.replace(/[+-0124679FHJLPTVXYZ]/g, "E");

const properties = {
color: `#${route.route_color || "000080"}`,
name: routeName,
nameUpper: routeName.length === 0 ? "" : routeNameUpper,
routeType: route.route_type,
textColor: `#${route.route_text_color || "eee"}`,
type: "route"
Expand All @@ -59,11 +49,37 @@ export function patternToRouteFeature(
* Obtains common layout options for route label layers.
*/
export function getRouteLayerLayout(textField: string): SymbolLayout {
// Generates a single icon based on the string length
function generateIcon(length: number) {
return [["==", ["length", ["get", textField]], length], `${length}`];
}

// Generates every icon length from 1-17. Anything higher renders a rectangle
const iconImage: Expression = [
"case",
...Array(17)
.fill(0)
.map((_, i) => generateIcon(i + 1))
.flat(),
"rect"
];

return {
"icon-image": iconImage,
"icon-optional": false,
"icon-allow-overlap": true,
"icon-rotation-alignment": "viewport",
"icon-text-fit-padding": [17, 17.5, 17, 17.5],
"icon-text-fit": "both",
"symbol-placement": "line-center",
"text-allow-overlap": true,
"text-field": ["get", textField],
"text-font": ["Open Sans Bold", "Arial Unicode MS Bold"],
"text-ignore-placement": true,
"text-justify": "left",
"text-line-height": 0.5,
"text-letter-spacing": 0,
"text-padding": 0,
"text-rotation-alignment": "viewport",
"text-size": 16
};
Expand Down