diff --git a/icons/poi_book_upright.svg b/icons/poi_book_upright.svg
new file mode 100644
index 000000000..b13bdc7bc
--- /dev/null
+++ b/icons/poi_book_upright.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/poi_car_repair.svg b/icons/poi_car_repair.svg
new file mode 100644
index 000000000..042bff803
--- /dev/null
+++ b/icons/poi_car_repair.svg
@@ -0,0 +1,4 @@
+
diff --git a/icons/poi_car_shop.svg b/icons/poi_car_shop.svg
new file mode 100644
index 000000000..cfcf82ba7
--- /dev/null
+++ b/icons/poi_car_shop.svg
@@ -0,0 +1,4 @@
+
diff --git a/icons/poi_hostel.svg b/icons/poi_hostel.svg
new file mode 100644
index 000000000..04d8bf388
--- /dev/null
+++ b/icons/poi_hostel.svg
@@ -0,0 +1,3 @@
+
diff --git a/icons/poi_hotel.svg b/icons/poi_hotel.svg
new file mode 100644
index 000000000..576f5a1d7
--- /dev/null
+++ b/icons/poi_hotel.svg
@@ -0,0 +1,3 @@
+
diff --git a/icons/poi_taxi.svg b/icons/poi_taxi.svg
new file mode 100644
index 000000000..c8dcb2193
--- /dev/null
+++ b/icons/poi_taxi.svg
@@ -0,0 +1,5 @@
+
diff --git a/icons/shield_us_az_scenic.svg b/icons/shield_us_az_scenic.svg
new file mode 100644
index 000000000..bff968fbd
--- /dev/null
+++ b/icons/shield_us_az_scenic.svg
@@ -0,0 +1,16 @@
+
diff --git a/scripts/taginfo_template.json b/scripts/taginfo_template.json
index 868967ced..32399044d 100644
--- a/scripts/taginfo_template.json
+++ b/scripts/taginfo_template.json
@@ -399,6 +399,14 @@
"doc_url": "https://openmaptiles.org/schema/#poi",
"icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_school.svg"
},
+ {
+ "key": "amenity",
+ "value": "taxi",
+ "object_types": ["node", "area"],
+ "description": "Taxi stands are marked by an icon representing the front view of a taxi cab.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_taxi.svg"
+ },
{
"key": "amenity",
"value": "kindergarten",
@@ -479,6 +487,38 @@
"doc_url": "https://openmaptiles.org/schema/#poi",
"icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_town_hall.svg"
},
+ {
+ "key": "tourism",
+ "value": "guest_house",
+ "object_types": ["node", "area"],
+ "description": "Guest houses are marked by an icon representing a person laying in a bed.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_hotel.svg"
+ },
+ {
+ "key": "tourism",
+ "value": "hostel",
+ "object_types": ["node", "area"],
+ "description": "Hostels are marked by an icon representing two people laying in a bunk bed.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_hostel.svg"
+ },
+ {
+ "key": "tourism",
+ "value": "hotel",
+ "object_types": ["node", "area"],
+ "description": "Hotels are marked by an icon representing a person laying in a bed.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_hotel.svg"
+ },
+ {
+ "key": "tourism",
+ "value": "motel",
+ "object_types": ["node", "area"],
+ "description": "Motels are marked by an icon representing a person laying in a bed.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_hotel.svg"
+ },
{
"key": "tourism",
"value": "museum",
@@ -487,7 +527,6 @@
"doc_url": "https://openmaptiles.org/schema/#poi",
"icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_museum.svg"
},
-
{
"key": "amenity",
"value": "clinic",
@@ -496,6 +535,14 @@
"doc_url": "https://openmaptiles.org/schema/#poi",
"icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_health_cross.svg"
},
+ {
+ "key": "amenity",
+ "value": "library",
+ "object_types": ["node", "area"],
+ "description": "Libraries are marked by an icon representing a book.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_book_upright.svg"
+ },
{
"key": "amenity",
"value": "parking",
@@ -632,6 +679,22 @@
"doc_url": "https://openmaptiles.org/schema/#poi",
"icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_pow_taoist.svg"
},
+ {
+ "key": "shop",
+ "value": "car",
+ "object_types": ["node", "area"],
+ "description": "Car dealerships are marked by an icon representing a front facing vehicle.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_car_shop.svg"
+ },
+ {
+ "key": "shop",
+ "value": "car_repair",
+ "object_types": ["node", "area"],
+ "description": "Car mechanic shops are marked by an icon representing a front facing vehicle with a wrench above it.",
+ "doc_url": "https://openmaptiles.org/schema/#poi",
+ "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_car_repair.svg"
+ },
{
"key": "shop",
"value": "supermarket",
diff --git a/shieldlib/src/screen_gfx.ts b/shieldlib/src/screen_gfx.ts
index eec3087e8..3be40e4be 100644
--- a/shieldlib/src/screen_gfx.ts
+++ b/shieldlib/src/screen_gfx.ts
@@ -5,7 +5,9 @@ import rgba from "color-rgba";
const defaultFontFamily = '"sans-serif-condensed", "Arial Narrow", sans-serif';
export const shieldFont = (size: number, fontFamily: string) =>
`condensed 500 ${size}px ${fontFamily || defaultFontFamily}`;
-export const fontSizeThreshold = 12;
+
+//If a computed shield font size is below this value, choose a wider shield if possible
+export const fontSizeThreshold = 11.8;
// Replaces `sourceVal` with a blend of `lightenVal` and `darkenVal` proportional to the brightness;
// i.e. white becomes `darkenVal`, black becomes `lightenVal`, and anit-aliased pixels remain anit-aliased
diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.js
index f8d3c342d..04ff352f4 100644
--- a/shieldlib/src/shield.js
+++ b/shieldlib/src/shield.js
@@ -76,18 +76,6 @@ function getDrawFunc(shieldDef) {
return ShieldDraw.blank;
}
-function drawShield(r, ctx, shieldDef, routeDef) {
- let bannerCount = getBannerCount(shieldDef);
- let yOffset = bannerCount * r.px(r.options.bannerHeight);
-
- //Shift canvas to draw shield below banner
- ctx.save();
- ctx.translate(0, yOffset);
- let drawFunc = getDrawFunc(shieldDef);
- drawFunc(r, ctx, routeDef.ref);
- ctx.restore();
-}
-
function getDrawHeight(r, shieldDef) {
if (typeof shieldDef.shapeBlank != "undefined") {
return ShieldDraw.shapeHeight(r, shieldDef.shapeBlank.drawFunc);
@@ -95,30 +83,7 @@ function getDrawHeight(r, shieldDef) {
return r.shieldSize();
}
-function drawShieldText(r, ctx, shieldDef, routeDef) {
- var bannerCount = getBannerCount(shieldDef);
- var shieldBounds = null;
-
- var shieldArtwork = getRasterShieldBlank(r, shieldDef, routeDef);
- let yOffset = bannerCount * r.px(r.options.bannerHeight);
-
- if (shieldArtwork == null) {
- ctx.translate(0, yOffset);
- let drawFunc = getDrawFunc(shieldDef);
- drawFunc(r, ctx, routeDef.ref);
- ctx.translate(0, -yOffset);
-
- shieldBounds = {
- width: ctx.canvas.width,
- height: getDrawHeight(r, shieldDef),
- };
- } else {
- shieldBounds = {
- width: shieldArtwork.data.width,
- height: shieldArtwork.data.height,
- };
- }
-
+function drawShieldText(r, ctx, shieldDef, routeDef, shieldBounds) {
if (shieldDef.notext) {
//If the shield definition says not to draw a ref, ignore ref
return ctx;
@@ -132,8 +97,6 @@ function drawShieldText(r, ctx, shieldDef, routeDef) {
shieldBounds
);
- textLayout.yBaseline += bannerCount * r.px(r.options.bannerHeight);
-
if (typeof r.options.SHIELD_TEXT_HALO_COLOR_OVERRIDE !== "undefined") {
ctx.strokeStyle = options.SHIELD_TEXT_HALO_COLOR_OVERRIDE;
ShieldText.drawShieldHaloText(r, ctx, routeDef.ref, textLayout);
@@ -150,8 +113,7 @@ function drawShieldText(r, ctx, shieldDef, routeDef) {
ctx.lineWidth = r.px(1);
ctx.strokeRect(
r.px(shieldDef.padding.left - 0.5),
- bannerCount * r.px(r.options.bannerHeight) +
- r.px(shieldDef.padding.top - 0.5),
+ r.px(shieldDef.padding.top - 0.5),
shieldBounds.width -
r.px(shieldDef.padding.left + shieldDef.padding.right - 1),
shieldBounds.height -
@@ -294,6 +256,17 @@ function getDrawnShieldBounds(r, shieldDef, ref) {
return { width, height };
}
+function bannerAreaHeight(r, bannerCount) {
+ if (bannerCount === 0) {
+ return 0;
+ }
+ return (
+ bannerCount * r.px(r.options.bannerHeight) +
+ //No padding after last banner
+ (bannerCount - 1) * r.px(r.options.bannerPadding)
+ );
+}
+
export function generateShieldCtx(r, routeDef) {
let shieldDef = getShieldDef(r.shieldDef, routeDef);
@@ -311,18 +284,25 @@ export function generateShieldCtx(r, routeDef) {
let width = r.shieldSize();
let height = r.shieldSize();
+ let shieldBounds = null;
+
if (sourceSprite == null) {
if (typeof shieldDef.shapeBlank != "undefined") {
let bounds = getDrawnShieldBounds(r, shieldDef, routeDef.ref);
width = bounds.width;
height = bounds.height;
}
+ shieldBounds = {
+ width: width,
+ height: getDrawHeight(r, shieldDef),
+ };
} else {
width = sourceSprite.data.width;
height = sourceSprite.data.height;
+ shieldBounds = { width, height };
}
- let bannerHeight = bannerCount * r.px(r.options.bannerHeight);
+ let bannerHeight = bannerAreaHeight(r, bannerCount);
height += bannerHeight;
//Generate empty canvas sized to the graphic
@@ -338,9 +318,15 @@ export function generateShieldCtx(r, routeDef) {
// Add the halo around modifier plaque text
drawBannerHalos(r, ctx, shieldDef);
+ //Shift canvas to draw shield below banner
+ ctx.save();
+ ctx.translate(0, bannerHeight);
+
if (sourceSprite == null) {
- drawShield(r, ctx, shieldDef, routeDef);
+ let drawFunc = getDrawFunc(shieldDef);
+ drawFunc(r, ctx, routeDef.ref);
} else {
+ //This is a raw copy, so the yOffset (bannerHeight) is needed
Gfx.transposeImageData(
ctx,
sourceSprite,
@@ -352,7 +338,9 @@ export function generateShieldCtx(r, routeDef) {
}
// Draw the shield text
- drawShieldText(r, ctx, shieldDef, routeDef);
+ drawShieldText(r, ctx, shieldDef, routeDef, shieldBounds);
+
+ ctx.restore();
// Add modifier plaque text
drawBanners(r, ctx, shieldDef);
diff --git a/shieldlib/src/shield_banner.ts b/shieldlib/src/shield_banner.ts
index 6961865b5..deb86599d 100644
--- a/shieldlib/src/shield_banner.ts
+++ b/shieldlib/src/shield_banner.ts
@@ -133,7 +133,7 @@ function drawBannerTextComponent(
textComponent: boolean
): void {
const bannerPadding = {
- top: r.options.bannerPadding,
+ top: 0,
bottom: 0,
left: 0,
right: 0,
@@ -161,7 +161,7 @@ function drawBannerTextComponent(
text,
textLayout.xBaseline,
textLayout.yBaseline +
- bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding)
+ bannerIndex * r.px(r.options.bannerHeight + r.options.bannerPadding)
);
} else {
ctx.shadowBlur = 0;
@@ -170,7 +170,7 @@ function drawBannerTextComponent(
text,
textLayout.xBaseline,
textLayout.yBaseline +
- bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding)
+ bannerIndex * r.px(r.options.bannerHeight + r.options.bannerPadding)
);
ctx.shadowColor = null;
diff --git a/shieldlib/src/shield_text.ts b/shieldlib/src/shield_text.ts
index 350f7f02c..5bc1071d0 100644
--- a/shieldlib/src/shield_text.ts
+++ b/shieldlib/src/shield_text.ts
@@ -137,6 +137,15 @@ function triangleDownTextConstraint(
};
}
+// Warning!!! Hack!!!
+function isRunningInWebKit(): boolean {
+ if (typeof window === "undefined") {
+ return false;
+ }
+ const userAgent = window.navigator.userAgent;
+ return /WebKit/i.test(userAgent) && !/Chrome/i.test(userAgent);
+}
+
/**
* Determines the position and font size to draw text so that it fits within
* a bounding box.
@@ -157,34 +166,49 @@ export function layoutShieldText(
textLayoutDef: TextLayout,
maxFontSize: number = 14
): TextPlacement {
- var padTop = r.px(padding.top) || 0;
- var padBot = r.px(padding.bottom) || 0;
- var padLeft = r.px(padding.left) || 0;
- var padRight = r.px(padding.right) || 0;
+ let padTop = r.px(padding.top) || 0;
+ let padBot = r.px(padding.bottom) || 0;
+ let padLeft = r.px(padding.left) || 0;
+ let padRight = r.px(padding.right) || 0;
- var maxFont = r.px(maxFontSize);
+ let maxFont = r.px(maxFontSize);
//Temporary canvas for text measurment
- var ctx = r.gfxFactory.createGraphics(bounds);
+ let ctx: CanvasRenderingContext2D = r.gfxFactory.createGraphics(bounds);
ctx.font = Gfx.shieldFont(Gfx.fontSizeThreshold, r.options.shieldFont);
- ctx.textAlign = "center";
+ ctx.textAlign = "left";
ctx.textBaseline = "top";
- var metrics = ctx.measureText(text);
+ let metrics: TextMetrics = ctx.measureText(text);
- var textWidth = metrics.width;
- var textHeight = metrics.actualBoundingBoxDescent;
+ let textWidth: number =
+ Math.abs(metrics.actualBoundingBoxLeft) +
+ Math.abs(metrics.actualBoundingBoxRight);
+ let textHeight: number =
+ Math.abs(metrics.actualBoundingBoxDescent) +
+ Math.abs(metrics.actualBoundingBoxAscent);
- var availHeight = bounds.height - padTop - padBot;
- var availWidth = bounds.width - padLeft - padRight;
+ //Adjust for excess descender text height across browsers
+ textHeight *= 0.9;
- var xBaseline = padLeft + availWidth / 2;
+ //Adjust for excess text height measured in Webkit engine specifically
+ if (isRunningInWebKit()) {
+ textHeight *= 0.54;
+ }
+
+ let availHeight: number = bounds.height - padTop - padBot;
+ let availWidth: number = bounds.width - padLeft - padRight;
+
+ let xBaseline: number = padLeft + availWidth / 2;
let textLayoutFunc = drawTextFunctions[textLayoutDef.constraintFunc];
- let textConstraint = textLayoutFunc(
- { height: availHeight, width: availWidth },
- { height: textHeight, width: textWidth },
+ let spaceAvail: Dimension = { height: availHeight, width: availWidth };
+ let measuredTextBounds: Dimension = { height: textHeight, width: textWidth };
+
+ let textConstraint: TextTransform = textLayoutFunc(
+ spaceAvail,
+ measuredTextBounds,
textLayoutDef.options
);
@@ -195,20 +219,23 @@ export function layoutShieldText(
);
ctx.font = Gfx.shieldFont(fontSize, r.options.shieldFont);
- ctx.textAlign = "center";
+ ctx.textAlign = "left";
ctx.textBaseline = "top";
metrics = ctx.measureText(text);
- textHeight = metrics.actualBoundingBoxDescent;
+ textHeight =
+ Math.abs(metrics.actualBoundingBoxDescent) +
+ Math.abs(metrics.actualBoundingBoxAscent);
let yBaseline: number;
switch (textConstraint.valign) {
case VerticalAlignment.Top:
- yBaseline = padTop;
+ yBaseline = padTop + metrics.actualBoundingBoxAscent;
break;
case VerticalAlignment.Bottom:
- yBaseline = padTop + availHeight - textHeight;
+ yBaseline =
+ padTop + availHeight - textHeight + metrics.actualBoundingBoxAscent;
break;
case VerticalAlignment.Middle:
default:
diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js
index a906aeb39..04bfd834b 100644
--- a/src/js/shield_defs.js
+++ b/src/js/shield_defs.js
@@ -653,6 +653,12 @@ export function loadShields() {
))
);
+ // Arizona
+ shields["US:AZ:Scenic"] = {
+ spriteBlank: "shield_us_az_scenic",
+ notext: true,
+ };
+
// Arkansas
shields["US:AR"] = {
spriteBlank: ["shield_us_ar_2", "shield_us_ar_3"],
@@ -3320,6 +3326,7 @@ export function loadShields() {
shields["omt-ie-national"] = roundedRectShield(
Color.shields.green,
+ Color.shields.white,
Color.shields.yellow
);
@@ -3535,6 +3542,7 @@ export function loadShields() {
shields["omt-gb-trunk"] = roundedRectShield(
Color.shields.green,
+ Color.shields.white,
Color.shields.yellow
);
diff --git a/src/layer/poi.js b/src/layer/poi.js
index c2447533d..cd9c3d1ed 100644
--- a/src/layer/poi.js
+++ b/src/layer/poi.js
@@ -35,6 +35,30 @@ var iconDefs = {
color: Color.poi.transport,
description: "Bus stop",
},
+ car_repair: {
+ classes: {
+ car: ["car_repair"],
+ },
+ sprite: "poi_car_repair",
+ color: Color.poi.consumer,
+ description: "Car mechanic",
+ },
+ car_shop: {
+ classes: {
+ car: ["car"],
+ },
+ sprite: "poi_car_shop",
+ color: Color.poi.consumer,
+ description: "Car dealership",
+ },
+ taxi: {
+ classes: {
+ office: ["taxi"],
+ },
+ sprite: "poi_taxi",
+ color: Color.poi.transport,
+ description: "Taxi stand",
+ },
coffee: {
classes: {
cafe: ["cafe"],
@@ -59,6 +83,30 @@ var iconDefs = {
color: Color.poi.infrastructure,
description: "Hospital",
},
+ hotel: {
+ classes: {
+ lodging: ["hotel", "motel", "guest_house"],
+ },
+ sprite: "poi_hotel",
+ color: Color.poi.consumer,
+ description: "Hotel",
+ },
+ hostel: {
+ classes: {
+ lodging: ["hostel"],
+ },
+ sprite: "poi_hostel",
+ color: Color.poi.consumer,
+ description: "Hostel",
+ },
+ library: {
+ classes: {
+ library: ["library"],
+ },
+ sprite: "poi_book_upright",
+ color: Color.poi.infrastructure,
+ description: "Library",
+ },
medical: {
classes: {
hospital: ["clinic"],
@@ -259,6 +307,10 @@ export const poi = {
...getSubclasses(iconDefs.bar),
...getSubclasses(iconDefs.coffee),
...getSubclasses(iconDefs.supermarket),
+ ...getSubclasses(iconDefs.car_shop),
+ ...getSubclasses(iconDefs.car_repair),
+ ...getSubclasses(iconDefs.hotel),
+ ...getSubclasses(iconDefs.hostel),
],
Color.poi.consumer,
[
@@ -266,6 +318,7 @@ export const poi = {
"bus_stop",
...getSubclasses(iconDefs.railway_station),
...getSubclasses(iconDefs.railway_stop),
+ ...getSubclasses(iconDefs.taxi),
],
Color.poi.transport,
["museum"],
@@ -276,6 +329,7 @@ export const poi = {
"police",
"school",
"college",
+ "library",
"townhall",
...getSubclasses(iconDefs.pow_christian),
...getSubclasses(iconDefs.pow_buddhist),
@@ -303,6 +357,7 @@ export const poi = {
[
"bus_stop",
"hospital",
+ "library",
"museum",
"police",
...getSubclasses(iconDefs.fuel),
@@ -320,7 +375,15 @@ export const poi = {
"tram_stop",
],
15,
- [...getSubclasses(iconDefs.bar), ...getSubclasses(iconDefs.coffee)],
+ [
+ ...getSubclasses(iconDefs.bar),
+ ...getSubclasses(iconDefs.coffee),
+ ...getSubclasses(iconDefs.car_shop),
+ ...getSubclasses(iconDefs.car_repair),
+ ...getSubclasses(iconDefs.taxi),
+ ...getSubclasses(iconDefs.hotel),
+ ...getSubclasses(iconDefs.hostel),
+ ],
16,
["clinic", "doctors", "parking"],
17,
diff --git a/test/sample_locations.json b/test/sample_locations.json
index 2ff11454f..8a150106d 100644
--- a/test/sample_locations.json
+++ b/test/sample_locations.json
@@ -48,6 +48,14 @@
"height": 400
}
},
+ {
+ "location": "17/40.753326/-73.982224",
+ "name": "library_z17",
+ "viewport": {
+ "width": 400,
+ "height": 400
+ }
+ },
{
"location": "13.25/49.552/5.8107",
"name": "europe_e-road_routes",
@@ -55,5 +63,29 @@
"width": 400,
"height": 400
}
+ },
+ {
+ "location": "16/38.896954/-77.108406",
+ "name": "virginia_",
+ "viewport": {
+ "width": 400,
+ "height": 400
+ }
+ },
+ {
+ "location": "16/38.906341/-77.025889",
+ "name": "dc_logan_circle",
+ "viewport": {
+ "width": 400,
+ "height": 400
+ }
+ },
+ {
+ "location": "16/40.755264/-73.987405",
+ "name": "manhattan_times_square",
+ "viewport": {
+ "width": 400,
+ "height": 400
+ }
}
]