diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 216ac2039..41a359531 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -269,6 +269,12 @@ The MUTCD’s standard colors are designed for high-contrast backgrounds and leg See the [developer tools](dev/README.md) for an importable, Inkscape-compatible palette file. +### Icon Grid Alignment + +There is a utility script called icon_grid that will generate a pixel grid on an SVG. This can be used to check how well the icon will align to the pixel grid. Run this utility as follows: + +`npm run icon_grid -- icons/poi_fuel.svg` + ### Font Sizes Shields should target 8-14px text actual-size character heights for readability: diff --git a/dev/README.md b/dev/README.md index 1b7c23781..5c81836a3 100644 --- a/dev/README.md +++ b/dev/README.md @@ -19,4 +19,4 @@ Map sample images can be generated with a script. See [sample_locations.json](te 1. Create a JSON file with the map location and clipping rectangles 2. Start the server in the background with `npm start &` 3. Configure playwright: either `npx playwright install chromium` or `export CHROME_BIN=/usr/bin/chromium` -4. Run the generate_samples script and pass the JSON file location: `npm run generate_samples -- test/sample_locations.json`` +4. Run the generate_samples script and pass the JSON file location: `npm run generate_samples -- chrome test/sample_locations.json``. You may also use "firefox" or "safari". 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_envelope.svg b/icons/poi_envelope.svg new file mode 100644 index 000000000..65113dabc --- /dev/null +++ b/icons/poi_envelope.svg @@ -0,0 +1,3 @@ + + + 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_ca_ns_s_bdolsd.svg b/icons/shield_ca_ns_s_bdolsd.svg new file mode 100644 index 000000000..fddb5e67c --- /dev/null +++ b/icons/shield_ca_ns_s_bdolsd.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_cet.svg b/icons/shield_ca_ns_s_cet.svg new file mode 100644 index 000000000..f8523af35 --- /dev/null +++ b/icons/shield_ca_ns_s_cet.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_ct.svg b/icons/shield_ca_ns_s_ct.svg new file mode 100644 index 000000000..fa2c05e5b --- /dev/null +++ b/icons/shield_ca_ns_s_ct.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_dnisd.svg b/icons/shield_ca_ns_s_dnisd.svg new file mode 100644 index 000000000..b46081a94 --- /dev/null +++ b/icons/shield_ca_ns_s_dnisd.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_et.svg b/icons/shield_ca_ns_s_et.svg new file mode 100644 index 000000000..23ec82fc3 --- /dev/null +++ b/icons/shield_ca_ns_s_et.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_fdlt.svg b/icons/shield_ca_ns_s_fdlt.svg new file mode 100644 index 000000000..8f057fcc1 --- /dev/null +++ b/icons/shield_ca_ns_s_fdlt.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_gt.svg b/icons/shield_ca_ns_s_gt.svg new file mode 100644 index 000000000..b907a78cf --- /dev/null +++ b/icons/shield_ca_ns_s_gt.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_ksd.svg b/icons/shield_ca_ns_s_ksd.svg new file mode 100644 index 000000000..5270f0e63 --- /dev/null +++ b/icons/shield_ca_ns_s_ksd.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_lr.svg b/icons/shield_ca_ns_s_lr.svg new file mode 100644 index 000000000..a478d5625 --- /dev/null +++ b/icons/shield_ca_ns_s_lr.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_md.svg b/icons/shield_ca_ns_s_md.svg new file mode 100644 index 000000000..5e35b59b6 --- /dev/null +++ b/icons/shield_ca_ns_s_md.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_mt.svg b/icons/shield_ca_ns_s_mt.svg new file mode 100644 index 000000000..2c36efcf6 --- /dev/null +++ b/icons/shield_ca_ns_s_mt.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/icons/shield_ca_ns_s_st.svg b/icons/shield_ca_ns_s_st.svg new file mode 100644 index 000000000..46ee7ebe1 --- /dev/null +++ b/icons/shield_ca_ns_s_st.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/icons/shield_us_az_scenic.svg b/icons/shield_us_az_scenic.svg new file mode 100644 index 000000000..d027e8acd --- /dev/null +++ b/icons/shield_us_az_scenic.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 7833bdf99..623957d73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "open": "^8.4.2", "pbf": "^3.2.1", "prettier": "^2.3.2", + "sharp": "^0.33.2", "shx": "^0.3.4", "svgo": "^2.8.0", "tsx": "^4.6.2", @@ -74,6 +75,91 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/@basemaps/sprites/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@basemaps/sprites/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@basemaps/sprites/node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@basemaps/sprites/node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", @@ -426,6 +512,456 @@ "node": ">=12" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", + "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz", + "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz", + "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz", + "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz", + "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz", + "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz", + "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz", + "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz", + "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz", + "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz", + "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz", + "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz", + "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz", + "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz", + "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz", + "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/runtime": "^0.45.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz", + "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz", + "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1990,10 +2526,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -3273,9 +3812,9 @@ "dev": true }, "node_modules/node-abi": { - "version": "3.51.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", - "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", + "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -4348,78 +4887,43 @@ "dev": true }, "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", + "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==", "dev": true, "hasInstallScript": true, "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=14.15.0" + "libvips": ">=8.15.1", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" - } - }, - "node_modules/sharp/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sharp/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sharp/node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.2", + "@img/sharp-darwin-x64": "0.33.2", + "@img/sharp-libvips-darwin-arm64": "1.0.1", + "@img/sharp-libvips-darwin-x64": "1.0.1", + "@img/sharp-libvips-linux-arm": "1.0.1", + "@img/sharp-libvips-linux-arm64": "1.0.1", + "@img/sharp-libvips-linux-s390x": "1.0.1", + "@img/sharp-libvips-linux-x64": "1.0.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1", + "@img/sharp-libvips-linuxmusl-x64": "1.0.1", + "@img/sharp-linux-arm": "0.33.2", + "@img/sharp-linux-arm64": "0.33.2", + "@img/sharp-linux-s390x": "0.33.2", + "@img/sharp-linux-x64": "0.33.2", + "@img/sharp-linuxmusl-arm64": "0.33.2", + "@img/sharp-linuxmusl-x64": "0.33.2", + "@img/sharp-wasm32": "0.33.2", + "@img/sharp-win32-ia32": "0.33.2", + "@img/sharp-win32-x64": "0.33.2" } }, "node_modules/shebang-command": { @@ -4703,9 +5207,9 @@ "dev": true }, "node_modules/streamx": { - "version": "2.15.5", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz", - "integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==", + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", + "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", "dev": true, "dependencies": { "fast-fifo": "^1.1.0", @@ -5003,6 +5507,13 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true, + "optional": true + }, "node_modules/tsx": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.6.2.tgz", diff --git a/package.json b/package.json index ecef53ce5..a55d536da 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "code_format:svgo": "svgo -q -f icons/", "extract_layer": "node scripts/extract_layer", "generate_samples": "tsx scripts/generate_samples.ts", + "icon_grid": "tsx scripts/icon_grid.ts", "presprites": "shx rm -rf dist/sprites", "serve": "tsx scripts/serve", "shields": "node scripts/generate_shield_defs.js -o dist/shields.json", @@ -69,6 +70,7 @@ "open": "^8.4.2", "pbf": "^3.2.1", "prettier": "^2.3.2", + "sharp": "^0.33.2", "shx": "^0.3.4", "svgo": "^2.8.0", "tsx": "^4.6.2", diff --git a/scripts/generate_samples.ts b/scripts/generate_samples.ts index 3093bba0f..2d0c6f5ce 100644 --- a/scripts/generate_samples.ts +++ b/scripts/generate_samples.ts @@ -1,5 +1,11 @@ import fs from "node:fs"; -import { chromium } from "@playwright/test"; +import { + Browser, + BrowserContext, + chromium, + firefox, + webkit, +} from "@playwright/test"; import type * as maplibre from "maplibre-gl"; // Declare a global augmentation for the Window interface @@ -33,23 +39,37 @@ const loadSampleLocations = (filePath: string): SampleSpecification[] => { const sampleFolder = "./samples"; -const jsonSampleLocations = process.argv[2] ?? "test/sample_locations.json"; +const jsonSampleLocations = process.argv[3] ?? "test/sample_locations.json"; console.log(`Loading sample locations from ${jsonSampleLocations}`); +const browserType = process.argv[2] ?? "chrome"; +console.log(`Using browser type: ${browserType}`); + const screenshots: SampleSpecification[] = loadSampleLocations(jsonSampleLocations); fs.mkdirSync(sampleFolder, { recursive: true }); -const browser = await chromium.launch({ - executablePath: process.env.CHROME_BIN, - args: ["--headless=new"], -}); -const context = await browser.newContext({ +let browser: Browser; + +switch (browserType) { + case "chrome": + default: + browser = await chromium.launch({ + executablePath: process.env.CHROME_BIN, + args: ["--headless=new"], + }); + break; + case "firefox": + browser = await firefox.launch(); + break; + case "safari": + browser = await webkit.launch(); +} + +const context: BrowserContext = await browser.newContext({ bypassCSP: true, - userAgent: - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36", }); const page = await context.newPage(); diff --git a/scripts/icon_grid.ts b/scripts/icon_grid.ts new file mode 100644 index 000000000..72996e16b --- /dev/null +++ b/scripts/icon_grid.ts @@ -0,0 +1,73 @@ +import sharp from "sharp"; +import fs from "fs"; +import path from "path"; + +const svgFilename = process.argv[2]; +const outputFilenameBase = path.parse(svgFilename).name; +const outputFilename = `${outputFilenameBase}_preview.png`; +const scale = 40; + +// Function to generate a pixel grid +function generateGridPattern(xOffset: number, yOffset: number): Buffer { + const gridSvg = ` + + + + `; + return Buffer.from(gridSvg); +} + +// Convert and scale SVG +async function convertAndScaleSVG(svgFilename: string): Promise { + const svgBuffer = fs.readFileSync(svgFilename); + + // Get dimensions of the original SVG + const metadata = await sharp(svgBuffer).metadata(); + const width = metadata.width! * scale; + const height = metadata.height! * scale; + + // Resize the SVG + const resizedSvgBuffer = await sharp(svgBuffer, { + density: 72 * scale, + }) + .resize(width, height) + .toBuffer(); + + const xOffset: number = metadata.width! % 2 == 0 ? scale / 2 : 0; + const yOffset: number = metadata.height! % 2 == 0 ? scale / 2 : 0; + + // Generate a pixel grid pattern + const gridPattern = generateGridPattern(xOffset, yOffset); + + // Composite the scaled image over the grid + await sharp({ + create: { + width, + height, + channels: 4, + background: { r: 211, g: 211, b: 211, alpha: 1 }, // Light gray to show white borders + }, + }) + .composite([ + { input: resizedSvgBuffer, blend: "over" }, + { input: gridPattern, tile: true, blend: "over" }, + ]) + .toFile(outputFilename); + console.log(`Wrote ${outputFilename}`); +} + +if (!svgFilename) { + console.error("Please provide an SVG filename."); + process.exit(1); +} + +try { + await convertAndScaleSVG(svgFilename); +} catch (error) { + console.error("Error: ", error); + process.exit(1); +} diff --git a/scripts/taginfo_template.json b/scripts/taginfo_template.json index 868967ced..672adcccb 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,46 @@ "doc_url": "https://openmaptiles.org/schema/#poi", "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_town_hall.svg" }, + { + "key": "amenity", + "value": "post_office", + "object_types": ["node", "area"], + "description": "Post offices are marked by an icon representing an envelope.", + "doc_url": "https://openmaptiles.org/schema/#poi", + "icon_url": "https://raw.githubusercontent.com/ZeLonewolf/openstreetmap-americana/main/icons/poi_envelope.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 +535,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 +543,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 +687,30 @@ "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": "books", + "object_types": ["node", "area"], + "description": "Bookstores 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": "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..8a8202cde 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 - @@ -203,6 +165,11 @@ function refForDefs(routeDef, shieldDef) { } function getShieldDef(shields, routeDef) { + if (!shields) { + //This occurs if the ShieldJSON is loaded from the network and hasn't loaded yet. + return null; + } + var shieldDef = shields[routeDef.network]; if (routeDef == null) { @@ -294,6 +261,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 +289,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 +323,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 +343,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_renderer.ts b/shieldlib/src/shield_renderer.ts index cc8c30e93..826555688 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -70,7 +70,7 @@ class MaplibreGLSpriteRepository implements SpriteRepository { options: StyleImageMetadata, update: boolean ): void { - if (update) { + if (update && this.map.listImages().includes(spriteID)) { this.map.removeImage(spriteID); this.map.addImage(spriteID, image); } else { @@ -106,6 +106,7 @@ export class AbstractShieldRenderer { this._renderContext.options = shieldSpec.options; this._renderContext.shieldDef = shieldSpec.networks; this._fontSpec = "1em " + shieldSpec.options.shieldFont; + console.log("ShieldJSON loaded"); if (this._map) { this.reloadShieldsOnFontLoad(); } 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/configs/config.aws.js b/src/configs/config.aws.js index 9727e7f64..51e032ebc 100644 --- a/src/configs/config.aws.js +++ b/src/configs/config.aws.js @@ -3,7 +3,7 @@ /* Planetiler tile server, hosted at AWS */ -const OPENMAPTILES_URL = "https://tile.ourmap.us/data/v3.json"; +const OPENMAPTILES_URL = "https://tile.ourmap.us/data/omt_3_15.json"; /* The following two variables override the color of the bounding box and halo of diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js index a906aeb39..03f6c1bd9 100644 --- a/src/js/shield_defs.js +++ b/src/js/shield_defs.js @@ -204,6 +204,9 @@ export function loadShields() { Color.shields.brown, Color.shields.white ); + shields["CA:NS:S"] = { + notext: true, + }; // Northwest Territories shields["CA:NT"] = { @@ -653,6 +656,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"], @@ -766,11 +775,14 @@ export function loadShields() { ["BUS"], Color.shields.green ); - shields["US:CA:CR"] = pentagonUpShield( - 3, - 15, - Color.shields.blue, - Color.shields.yellow + ["CR", "Sierra"].forEach( + (county) => + (shields[`US:CA:${county}`] = pentagonUpShield( + 3, + 15, + Color.shields.blue, + Color.shields.yellow + )) ); shields["US:CA:Mendocino"] = roundedRectShield( Color.shields.green, @@ -841,6 +853,10 @@ export function loadShields() { Color.shields.green, Color.shields.white ); + shields["US:CO:Weld:WCP"] = { + ...pentagonUpShield(3, 15, Color.shields.blue, Color.shields.green), + textHaloColor: Color.shields.white, + }; // Connecticut shields["US:CT"] = roundedRectShield( @@ -1128,8 +1144,8 @@ export function loadShields() { "Webster", "Winn", ].forEach( - (county) => - (shields[`US:LA:${county}`] = pentagonUpShield( + (parish) => + (shields[`US:LA:${parish}`] = pentagonUpShield( 3, 15, Color.shields.blue, @@ -1266,7 +1282,6 @@ export function loadShields() { "Lake", "Lake_of_the_Woods", "Le_Sueur", - "Lincoln", "Lyon", "Mahnomen", "Marshall", @@ -1292,7 +1307,6 @@ export function loadShields() { "Redwood", "Renville", "Rice", - "Rock", "Roseau", "Saint_Louis", "Scott", @@ -1315,11 +1329,7 @@ export function loadShields() { "Yellow_Medicine", ].forEach( (county) => - ([ - shields[`US:MN:${county}:CSAH`], - shields[`US:MN:${county}:CR`], - shields[`US:MN:${county}:Park_Access`], - ] = [ + ([shields[`US:MN:${county}:CSAH`], shields[`US:MN:${county}:CR`]] = [ pentagonUpShield( 3, 15, @@ -1328,15 +1338,32 @@ export function loadShields() { Color.shields.white ), roundedRectShield(Color.shields.white, Color.shields.black), - trapezoidDownShield( - 10, - Color.shields.brown, - Color.shields.white, - Color.shields.white, - 2 - ), ]) ); + ["CSAH", "CR"].forEach( + (network) => + (shields[`US:MN:Lincoln:${network}`] = pentagonUpShield( + 3, + 15, + Color.shields.blue, + Color.shields.yellow, + Color.shields.white + )) + ); + ["CSAH", "CR"].forEach( + (network) => + (shields[`US:MN:Rock:${network}`] = roundedRectShield( + Color.shields.white, + Color.shields.black + )) + ); + shields[`US:MN:Hennepin:Park_Access`] = trapezoidDownShield( + 10, + Color.shields.brown, + Color.shields.white, + Color.shields.white, + 2 + ); // Missouri shields["US:MO"] = { @@ -1400,6 +1427,11 @@ export function loadShields() { // Mississippi shields["US:MS"] = ovalShield(Color.shields.white, Color.shields.black); + shields["US:MS:Scenic"] = banneredShield( + ovalShield(Color.shields.white, Color.shields.blue), + ["SCEN"], + Color.shields.blue + ); [ "Alcorn", "Calhoun", @@ -1632,6 +1664,13 @@ export function loadShields() { ["TRK"], Color.shields.blue ); + ["Sussex", "Warren"].forEach( + (county) => + (shields[`US:NJ:${county}:NPS`] = pillShield( + Color.shields.brown, + Color.shields.white + )) + ); // New Mexico shields["US:NM"] = pillShield( @@ -2236,6 +2275,10 @@ export function loadShields() { bottom: 8, }, }; + shields["US:SD:Custer:NPS"] = roundedRectShield( + Color.shields.brown, + Color.shields.yellow + ); // Tennessee shields["US:TN:primary"] = { @@ -2449,11 +2492,14 @@ export function loadShields() { bottom: 5, }, }; - shields["US:UT:Wayne"] = pentagonUpShield( - 3, - 15, - Color.shields.blue, - Color.shields.yellow + ["Wayne", "Washington"].forEach( + (county) => + (shields[`US:UT:${county}`] = pentagonUpShield( + 3, + 15, + Color.shields.blue, + Color.shields.yellow + )) ); // Virginia @@ -3320,6 +3366,7 @@ export function loadShields() { shields["omt-ie-national"] = roundedRectShield( Color.shields.green, + Color.shields.white, Color.shields.yellow ); @@ -3535,6 +3582,7 @@ export function loadShields() { shields["omt-gb-trunk"] = roundedRectShield( Color.shields.green, + Color.shields.white, Color.shields.yellow ); @@ -3616,6 +3664,45 @@ export function loadShields() { // Ref-specific cases. Each entry should be documented in CONTRIBUTE.md + shields["CA:NS:S"].overrideByName = { + "Bras d'Or Lakes Scenic Drive": { + spriteBlank: "shield_ca_ns_s_bdolsd", + }, + "Ceilidh Trail": { + spriteBlank: "shield_ca_ns_s_cet", + }, + "Cabot Trail": { + spriteBlank: "shield_ca_ns_s_ct", + }, + "Digby Neck and Islands Scenic Drive": { + spriteBlank: "shield_ca_ns_s_dnisd", + }, + "Evangeline Trail": { + spriteBlank: "shield_ca_ns_s_et", + }, + "Fleur-de-lis Trail": { + spriteBlank: "shield_ca_ns_s_fdlt", + }, + "Glooscap Trail": { + spriteBlank: "shield_ca_ns_s_gt", + }, + "Kejimkujik Scenic Drive": { + spriteBlank: "shield_ca_ns_s_ksd", + }, + "Lighthouse Route": { + spriteBlank: "shield_ca_ns_s_lr", + }, + "Marine Drive": { + spriteBlank: "shield_ca_ns_s_md", + }, + "Marconi Trail": { + spriteBlank: "shield_ca_ns_s_mt", + }, + "Sunrise Trail": { + spriteBlank: "shield_ca_ns_s_st", + }, + }; + shields["CA:ON:primary"].overrideByRef = { QEW: { textColor: Color.shields.blue, @@ -3652,23 +3739,13 @@ export function loadShields() { }; shields["US:KY:Parkway"].refsByName = { - // FIXME: This object contains both spelled-out and abbreviated road - // names to accommodate both the abbreviated names from OpenMapTiles and - // the spelled-out names from Planetiler. - // https://github.com/onthegomap/planetiler/issues/14 "Audubon Parkway": "AU", "Bluegrass Parkway": "BG", - "Bluegrass Pkwy": "BG", "Cumberland Parkway": "LN", - "Cumberland Pkwy": "LN", "Hal Rogers Parkway": "HR", - "Hal Rogers Pkwy": "HR", "Mountain Parkway": "MP", - "Mountain Pkwy": "MP", "Purchase Parkway": "JC", - "Purchase Pkwy": "JC", "Western Kentucky Parkway": "WK", - "Western Kentucky Pkwy": "WK", }; shields["US:CT:Parkway"].overrideByName = { @@ -3708,15 +3785,13 @@ export function loadShields() { }; shields["US:NY:Parkway"].refsByName = { - "Bear Mountain Parkway": "BMP", - "Bronx and Pelham Parkway": "PP", + "Bear Mountain State Parkway": "BMP", "Bronx River Parkway": "BRP", "Cross County Parkway": "CCP", "Hutchinson River Parkway": "HRP", "Korean War Veterans Parkway": "KWVP", "Mosholu Parkway": "MP", "Niagara Scenic Parkway": "NSP", - "Pelham Parkway": "PP", "Saw Mill River Parkway": "SMP", "Sprain Brook Parkway": "SBP", "Taconic State Parkway": "TSP", @@ -3736,14 +3811,7 @@ export function loadShields() { "Fort Bend Westpark Tollway": "WPT", }; shields["US:TX:Harris:HCTRA"].refsByName = { - "East Sam Houston Tollway North": "SHT", - "East Sam Houston Tollway South": "SHT", - "North Sam Houston Tollway East": "SHT", - "North Sam Houston Tollway West": "SHT", - "South Sam Houston Tollway East": "SHT", - "South Sam Houston Tollway West": "SHT", - "West Sam Houston Tollway North": "SHT", - "West Sam Houston Tollway South": "SHT", + "Sam Houston Tollway": "SHT", "Fort Bend Toll Road": "FBTR", "Hardy Toll Road": "HTR", "Tomball Tollway": "TBT", diff --git a/src/js/shield_format.ts b/src/js/shield_format.ts index ae6ffdd32..10368bbf5 100644 --- a/src/js/shield_format.ts +++ b/src/js/shield_format.ts @@ -3,6 +3,8 @@ import type { RouteParser, } from "@americana/maplibre-shield-generator"; +import { parseImageName } from "../layer/highway_shield.js"; + export const shieldPredicate: StringPredicate = (imageID: string) => imageID && imageID.startsWith("shield"); @@ -14,16 +16,8 @@ export const networkPredicate: StringPredicate = (network: string) => export const routeParser: RouteParser = { parse: (id: string) => { - //Americana format is `${shield}\n${network}=${ref}\n${name}` - let id_parts: string[] = id.split("\n"); - let network_ref = id_parts[1].split("="); - - return { - network: network_ref[0], - ref: network_ref[1], - name: id_parts[2], - }; + return parseImageName(id); }, format: (network: string, ref: string, name: string) => - `shield\n${network}=${ref}\n${name}`, + `shield\n${network}\n${ref}\n${name}\n`, }; diff --git a/src/layer/highway_shield.js b/src/layer/highway_shield.js index 319a37224..6c24abb43 100644 --- a/src/layer/highway_shield.js +++ b/src/layer/highway_shield.js @@ -1,33 +1,23 @@ "use strict"; -export const namedRouteNetworks = [ - "US:CT:Parkway", - "US:KY:Parkway", - "US:NH:Turnpike", - "US:NY:Parkway", - "US:TX:Fort_Bend:FBCTRA", - "US:TX:Harris:HCTRA", -]; +const orderedRouteAttributes = ["network", "ref", "name", "color"]; export function getImageNameExpression(routeIndex) { - return [ - "concat", - "shield\n", - ["get", "route_" + routeIndex], - [ - "match", - ["get", "route_" + routeIndex], - namedRouteNetworks.map((n) => n + "="), - ["concat", "\n", ["get", "name"]], - "", - ], - ]; + let concat = ["concat", "shield"]; + for (let attr of orderedRouteAttributes) { + concat.push("\n"); + concat.push(["coalesce", ["get", `route_${routeIndex}_${attr}`], ""]); + } + return concat; } function routeConcurrency(routeIndex) { return [ "case", - ["!=", ["get", "route_" + routeIndex], null], + [ + "any", + ...orderedRouteAttributes.map((a) => ["has", `route_${routeIndex}_${a}`]), + ], ["image", getImageNameExpression(routeIndex)], ["literal", ""], ]; @@ -37,12 +27,16 @@ function routeConcurrency(routeIndex) { * Returns a structured representation of the given image name. * * @param name An image name in the format returned by `routeConcurrency`. + * @return An object with the keys in `orderedRouteAttributes` plus the full image name in `imageName`. */ export function parseImageName(imageName) { let lines = imageName.split("\n"); - let [, network, ref] = lines[1].match(/^(.*?)=(.*)/) || []; - let name = lines[2]; - return { imageName, network, ref, name }; + lines.shift(); // "shield" + let parsed = Object.fromEntries( + orderedRouteAttributes.map((a, i) => [a, lines[i]]) + ); + parsed.imageName = imageName; + return parsed; } let shieldTextField = ["format"]; @@ -113,11 +107,6 @@ export const shield = { }, filter: [ "any", - ["has", "route_1"], - ["has", "route_2"], - ["has", "route_3"], - ["has", "route_4"], - ["has", "route_5"], - ["has", "route_6"], + ...orderedRouteAttributes.map((a) => ["has", `route_1_${a}`]), ], }; diff --git a/src/layer/poi.js b/src/layer/poi.js index c6a9ff369..64279b01d 100644 --- a/src/layer/poi.js +++ b/src/layer/poi.js @@ -19,6 +19,14 @@ var iconDefs = { color: Color.poi.consumer, description: "Bar or pub", }, + bookstore: { + classes: { + library: ["books"], + }, + sprite: "poi_book_upright", + color: Color.poi.consumer, + description: "Bookstore", + }, bus_station: { classes: { bus: ["bus_station"], @@ -35,6 +43,22 @@ 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", + }, cemetery: { classes: { cemetery: ["cemetery"], @@ -42,6 +66,14 @@ var iconDefs = { sprite: "poi_gravestone", color: Color.poi.outdoor, decription: "Cemetery", + }, + taxi: { + classes: { + office: ["taxi"], + }, + sprite: "poi_taxi", + color: Color.poi.transport, + description: "Taxi stand", }, coffee: { classes: { @@ -67,6 +99,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"], @@ -100,6 +156,14 @@ var iconDefs = { color: Color.poi.infrastructure, description: "Police station", }, + post_office: { + classes: { + post: ["post_office"], + }, + sprite: "poi_envelope", + color: Color.poi.infrastructure, + description: "Post office", + }, pow_buddhist: { classes: { place_of_worship: ["buddhist"], @@ -265,8 +329,13 @@ export const poi = { [ ...getSubclasses(iconDefs.fuel), ...getSubclasses(iconDefs.bar), + ...getSubclasses(iconDefs.bookstore), ...getSubclasses(iconDefs.coffee), ...getSubclasses(iconDefs.supermarket), + ...getSubclasses(iconDefs.car_shop), + ...getSubclasses(iconDefs.car_repair), + ...getSubclasses(iconDefs.hotel), + ...getSubclasses(iconDefs.hostel), ], Color.poi.consumer, [ @@ -274,6 +343,7 @@ export const poi = { "bus_stop", ...getSubclasses(iconDefs.railway_station), ...getSubclasses(iconDefs.railway_stop), + ...getSubclasses(iconDefs.taxi), ], Color.poi.transport, ["museum"], @@ -283,8 +353,10 @@ export const poi = { "parking", "police", "school", - "college", + ...getSubclasses(iconDefs.college), + "library", "townhall", + ...getSubclasses(iconDefs.post_office), ...getSubclasses(iconDefs.pow_christian), ...getSubclasses(iconDefs.pow_buddhist), ...getSubclasses(iconDefs.pow_hindu), @@ -306,16 +378,24 @@ export const poi = { [ "match", ["get", "subclass"], - ["station", "halt"], + [ + ...getSubclasses(iconDefs.college), + ], + 10, + [ + "station", + "halt"], 12, - ["bus_station", "subway", ...getSubclasses(iconDefs.college)], + ["bus_station", "subway"], 14, [ "bus_stop", "hospital", + "library", "museum", "police", ...getSubclasses(iconDefs.fuel), + ...getSubclasses(iconDefs.post_office), ...getSubclasses(iconDefs.pow_buddhist), ...getSubclasses(iconDefs.pow_christian), ...getSubclasses(iconDefs.pow_hindu), @@ -332,8 +412,14 @@ export const poi = { 15, [ ...getSubclasses(iconDefs.bar), - ...getSubclasses(iconDefs.coffee), + ...getSubclasses(iconDefs.bookstore), ...getSubclasses(iconDefs.cemetery), + ...getSubclasses(iconDefs.coffee), + ...getSubclasses(iconDefs.car_shop), + ...getSubclasses(iconDefs.car_repair), + ...getSubclasses(iconDefs.taxi), + ...getSubclasses(iconDefs.hotel), + ...getSubclasses(iconDefs.hostel), ], 16, ["clinic", "doctors", "parking"], diff --git a/test/sample_locations.json b/test/sample_locations.json index 2ff11454f..dd5ba8dfe 100644 --- a/test/sample_locations.json +++ b/test/sample_locations.json @@ -48,6 +48,14 @@ "height": 400 } }, + { + "location": "17/40.753922/-73.984244", + "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 + } } ] diff --git a/test/shield_format/shield_format.spec.ts b/test/shield_format/shield_format.spec.ts index 0c72201ef..f79177a7e 100644 --- a/test/shield_format/shield_format.spec.ts +++ b/test/shield_format/shield_format.spec.ts @@ -1,10 +1,11 @@ import { expect } from "chai"; import { shieldPredicate, routeParser } from "../../src/js/shield_format.js"; -const image_id_I95 = "shield\nUS:I=95"; +const image_id_I95 = "shield\nUS:I\n95\nEye Ninety-Five"; const route_def_I95 = { network: "US:I", ref: "95", + name: "Eye Ninety-Five", }; describe("shield_format", function () { @@ -19,6 +20,7 @@ describe("shield_format", function () { let extractedDef = routeParser.parse(image_id_I95); expect(extractedDef.network).to.be.equal(route_def_I95.network); expect(extractedDef.ref).to.be.equal(route_def_I95.ref); + expect(extractedDef.name).to.be.equal(route_def_I95.name); }); }); }); diff --git a/test/spec/highway_shield.js b/test/spec/highway_shield.js index 14df94866..20939d3f4 100644 --- a/test/spec/highway_shield.js +++ b/test/spec/highway_shield.js @@ -17,20 +17,20 @@ describe("highway_shield", function () { .createExpression(HighwayShieldLayers.getImageNameExpression(1)) .value.expression.evaluate(expressionContext(properties)); - let expectImageName = (network, ref, name, expectedImageName) => { + let expectImageName = (network, ref, name, color, expectedImageName) => { let properties = { - route_1: `${network || ""}=${ref || ""}`, - name: name || null, + route_1_network: network || "", + route_1_ref: ref || "", + route_1_name: name || "", + route_1_color: color || "", }; let evaluated = evaluatedExpression(properties); let expectedProperties = { imageName: expectedImageName, network: network || "", ref: ref || "", - name: - !ref && HighwayShieldLayers.namedRouteNetworks.includes(network) - ? name - : undefined, + name: name || "", + color: color || "", }; expect(HighwayShieldLayers.parseImageName(evaluated)).to.be.deep.equal( expectedProperties @@ -38,28 +38,54 @@ describe("highway_shield", function () { }; it("parses an image name for a numbered route", function () { - expectImageName("NET", "REF", undefined, "shield\nNET=REF"); - expectImageName("NET", "REF", "NAME", "shield\nNET=REF"); + expectImageName( + "NET", + "REF", + undefined, + undefined, + "shield\nNET\nREF\n\n" + ); + expectImageName( + "NET", + "REF", + "NAME", + undefined, + "shield\nNET\nREF\nNAME\n" + ); }); it("parses an image name for an unnumbered route", function () { - expectImageName("NET", undefined, undefined, "shield\nNET="); + expectImageName( + "NET", + undefined, + undefined, + undefined, + "shield\nNET\n\n\n" + ); }); it("parses an image name for a named route", function () { expectImageName( "US:KY:Parkway", undefined, "NAME", - "shield\nUS:KY:Parkway=\nNAME" + undefined, + "shield\nUS:KY:Parkway\n\nNAME\n" ); expectImageName( "US:KY:Parkway", "REF", "NAME", - "shield\nUS:KY:Parkway=REF" + undefined, + "shield\nUS:KY:Parkway\nREF\nNAME\n" ); }); it("parses an image name for a network-independent route", function () { - expectImageName(undefined, "REF", "NAME", "shield\n=REF"); + expectImageName( + undefined, + "REF", + "NAME", + undefined, + "shield\n\nREF\nNAME\n" + ); }); }); }); diff --git a/test/spec/shield.js b/test/spec/shield.js index 2fb4b4bf2..29beb569d 100644 --- a/test/spec/shield.js +++ b/test/spec/shield.js @@ -25,11 +25,11 @@ const shieldRenderer = new ShieldRenderer(shields, routeParser) const handler = shieldRenderer.getStyleImageMissingHandler(); -handler({ id: "shield\nBAB=5" }); -handler({ id: "shield\nUS:RI=" }); -handler({ id: "shield\nUS:RI=ABC123" }); -handler({ id: "shield\nUS:RI=Equator" }); -handler({ id: "shield\nrwn=" }); +handler({ id: "shield\nBAB\n5\n\n" }); +handler({ id: "shield\nUS:RI\n\n\n" }); +handler({ id: "shield\nUS:RI\nABC123\n\n" }); +handler({ id: "shield\nUS:RI\nEquator\n\n" }); +handler({ id: "shield\nrwn\n\n\n" }); handler({ id: "foo" }); function isBlankSprite(id) { @@ -39,8 +39,8 @@ function isBlankSprite(id) { describe("shield", function () { describe("#isValidNetwork", function () { it("rejects a recreational network", function () { - expect(isBlankSprite("shield\nBAB=5")).to.be.false; - expect(isBlankSprite("shield\nrwn=")).to.be.true; + expect(isBlankSprite("shield\nBAB\n5\n\n")).to.be.false; + expect(isBlankSprite("shield\nrwn\n\n\n")).to.be.true; }); it("rejects other missing image prefixes", function () { expect(mockRepo.hasSprite("foo")).to.be.false; @@ -48,12 +48,12 @@ describe("shield", function () { }); describe("#isValidRef", function () { it("rejects an empty ref", function () { - expect(isBlankSprite("shield\nUS:RI=")).to.be.true; + expect(isBlankSprite("shield\nUS:RI\n\n\n")).to.be.true; }); it("rejects a long ref", function () { - expect(mockRepo.hasSprite("shield\nUS:RI=ABC123")).to.be.true; - expect(isBlankSprite("shield\nUS:RI=ABC123")).to.be.false; - expect(isBlankSprite("shield\nUS:RI=Equator")).to.be.true; + expect(mockRepo.hasSprite("shield\nUS:RI\nABC123\n\n")).to.be.true; + expect(isBlankSprite("shield\nUS:RI\nABC123\n\n")).to.be.false; + expect(isBlankSprite("shield\nUS:RI\nEquator\n\n")).to.be.true; }); }); });