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;
});
});
});