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_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/shield_us_az_scenic.svg b/icons/shield_us_az_scenic.svg index bff968fbd..d027e8acd 100644 --- a/icons/shield_us_az_scenic.svg +++ b/icons/shield_us_az_scenic.svg @@ -1,16 +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 a1891714f..672adcccb 100644 --- a/scripts/taginfo_template.json +++ b/scripts/taginfo_template.json @@ -487,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", @@ -495,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", @@ -648,6 +687,14 @@ "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", diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.js index 04ff352f4..8a8202cde 100644 --- a/shieldlib/src/shield.js +++ b/shieldlib/src/shield.js @@ -165,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) { diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 3596d0875..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(); } @@ -245,7 +246,7 @@ export class AbstractShieldRenderer { } /** Get a blank route shield graphics context in a specified size */ - public createGraphics(bounds: Bounds) { + public createGraphics(bounds: Bounds): CanvasRenderingContext2D { return this._renderContext.gfxFactory.createGraphics(bounds); } 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/poi.js b/src/js/poi.js deleted file mode 100644 index ff3bc6c7c..000000000 --- a/src/js/poi.js +++ /dev/null @@ -1,41 +0,0 @@ -import { transposeImageData } from "@americana/maplibre-shield-generator"; - -export function missingIconHandler(shieldRenderer, map, e) { - try { - missingIconLoader(shieldRenderer, map, e); - } catch (err) { - console.error(`Exception while loading image ‘${e?.id}’:\n`, err); - } -} - -export function missingIconLoader(shieldRenderer, map, e) { - var sprite = e.id.split("\n")[1].split("=")[1]; - var color = e.id.split("\n")[2].split("=")[1]; - - var sourceSprite = map.style.getImage(sprite); - - var width = sourceSprite.data.width; - var height = sourceSprite.data.height; - - var ctx = shieldRenderer.createGraphics({ width, height }); - transposeImageData(ctx, sourceSprite, 0, false, color); - - if (ctx == null) { - // Want to return null here, but that gives a corrupted display. See #243 - console.warn("Didn't produce an icon for", JSON.stringify(e.id)); - ctx = shieldRenderer.createGraphics({ width: 1, height: 1 }); - } - - const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); - map.addImage( - e.id, - { - width: ctx.canvas.width, - height: ctx.canvas.height, - data: imgData.data, - }, - { - pixelRatio: shieldRenderer.pixelRatio(), - } - ); -} diff --git a/src/js/poi.ts b/src/js/poi.ts new file mode 100644 index 000000000..8f6da6688 --- /dev/null +++ b/src/js/poi.ts @@ -0,0 +1,66 @@ +import { + transposeImageData, + AbstractShieldRenderer, +} from "@americana/maplibre-shield-generator"; +import { Map, MapStyleImageMissingEvent, StyleImage } from "maplibre-gl"; + +export function missingIconHandler( + shieldRenderer: AbstractShieldRenderer, + map: Map, + e: MapStyleImageMissingEvent +) { + try { + missingIconLoader(shieldRenderer, map, e); + } catch (err) { + console.error(`Exception while loading image ‘${e?.id}’:\n`, err); + } +} + +export function missingIconLoader( + shieldRenderer: AbstractShieldRenderer, + map: Map, + e: MapStyleImageMissingEvent +) { + const sprite: string = e.id.split("\n")[1].split("=")[1]; + const color: string = e.id.split("\n")[2].split("=")[1]; + + const sourceSprite: StyleImage = map.style.getImage(sprite); + + if (!sourceSprite) { + console.error(`No such sprite ${sprite}`); + return; + } + + const width: number = sourceSprite.data.width; + const height: number = sourceSprite.data.height; + + let ctx: CanvasRenderingContext2D = shieldRenderer.createGraphics({ + width, + height, + }); + transposeImageData(ctx, sourceSprite, 0, false, color); + + if (ctx == null) { + // Want to return null here, but that gives a corrupted display. See #243 + console.warn("Didn't produce an icon for", JSON.stringify(e.id)); + ctx = shieldRenderer.createGraphics({ width: 1, height: 1 }); + } + + const imgData: ImageData = ctx.getImageData( + 0, + 0, + ctx.canvas.width, + ctx.canvas.height + ); + map.addImage( + e.id, + { + width: ctx.canvas.width, + height: ctx.canvas.height, + data: imgData.data, + }, + { + pixelRatio: shieldRenderer.pixelRatio(), + } + ); +} diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js index 05091733b..baa33ab77 100644 --- a/src/js/shield_defs.js +++ b/src/js/shield_defs.js @@ -850,6 +850,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( @@ -1641,6 +1645,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( @@ -2245,6 +2256,10 @@ export function loadShields() { bottom: 8, }, }; + shields["US:SD:Custer:NPS"] = roundedRectShield( + Color.shields.brown, + Color.shields.yellow + ); // Tennessee shields["US:TN:primary"] = { 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..2e2d65d75 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,14 @@ 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 +105,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 59b4dc77e..885c12d30 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"], @@ -83,6 +91,22 @@ 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"], @@ -124,6 +148,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"], @@ -289,10 +321,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, [ @@ -313,6 +348,7 @@ export const poi = { "college", "library", "townhall", + ...getSubclasses(iconDefs.post_office), ...getSubclasses(iconDefs.pow_christian), ...getSubclasses(iconDefs.pow_buddhist), ...getSubclasses(iconDefs.pow_hindu), @@ -343,6 +379,7 @@ export const poi = { "museum", "police", ...getSubclasses(iconDefs.fuel), + ...getSubclasses(iconDefs.post_office), ...getSubclasses(iconDefs.pow_buddhist), ...getSubclasses(iconDefs.pow_christian), ...getSubclasses(iconDefs.pow_hindu), @@ -359,10 +396,13 @@ export const poi = { 15, [ ...getSubclasses(iconDefs.bar), + ...getSubclasses(iconDefs.bookstore), ...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 8b448923e..dd5ba8dfe 100644 --- a/test/sample_locations.json +++ b/test/sample_locations.json @@ -49,7 +49,7 @@ } }, { - "location": "17/40.753326/-73.982224", + "location": "17/40.753922/-73.984244", "name": "library_z17", "viewport": { "width": 400, @@ -71,5 +71,21 @@ "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..cf7c38570 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,30 @@ 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; }); }); });