Skip to content

Commit f0bd681

Browse files
committed
remove protosprites dependency; add sprite-loading code to tasks.
fix up types related to CSS font loading. add potpack dependency.
1 parent 1e0e6a9 commit f0bd681

7 files changed

+265
-41
lines changed

SPRITES.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
## Overview
2+
3+
Protomaps.js includes a sprite-like system for sheets of icons. A sheet is just a single-file HTML document containing SVGs; sheets are loaded and baked into bitmaps on a canvas, where they can be sampled efficiently via Canvas 2D `drawImage`. This is useful for resolution-independent map symbology inside web browsers.
4+
5+
## Format
6+
7+
A sheet is a collection of SVGs organized in a specific way:
8+
9+
- It must be an HTML document with all SVGs as children of the `body` element
10+
- Each SVG element must have a width and a height in px, interpreted as CSS (not device) pixels
11+
- Each SVG must have a unique ID attribute
12+
13+
Example of a valid sheet:
14+
15+
```html
16+
<html>
17+
<body>
18+
<svg
19+
id="foobar"
20+
width="20px"
21+
height="20px"
22+
version="1.1"
23+
xmlns="http://www.w3.org/2000/svg"
24+
>
25+
<circle cx="10" cy="10" r="8" fill="green" />
26+
</svg>
27+
</body>
28+
</html>
29+
```
30+
31+
SVGs can also be defined via inline strings, avoiding a fetch request.
32+
33+
```js
34+
const ICONS = `
35+
<html>
36+
<body>
37+
<svg id="foobar" .../>
38+
</body>
39+
</html>
40+
`;
41+
let sheet = new Sheet(ICONS);
42+
```
43+
44+
## Library usage
45+
46+
a Sheet instance asynchronously loads the sheet and renders it to an off-screen Canvas context at device-native resolution. The canvas can then be sampled by icon name via the `get` method:
47+
48+
```js
49+
let sheet = new Sheet("sheet.html");
50+
let canvas = document.getElementById("canvas");
51+
let ctx = canvas.getContext("2d");
52+
sheet.load().then(() => {
53+
let s = sheet.get("foobar");
54+
ctx.drawImage(s.canvas, s.x, s.y, s.w, s.h, 0, 0, s.w, s.h);
55+
});
56+
```

examples/sprites.html

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<html>
2+
<head>
3+
<meta charset="utf-8" />
4+
<meta name="viewport" content="width=device-width, initial-scale=1">
5+
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"/>
6+
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
7+
<script src="https://unpkg.com/[email protected]/leaflet-hash.js"></script>
8+
<!-- <script src="https://unpkg.com/protomaps@latest/dist/protomaps.min.js"></script> -->
9+
<script src="../dist/protomaps.js"></script>
10+
<style>
11+
body, #map {
12+
height:100vh;
13+
margin:0px;
14+
}
15+
</style>
16+
</head>
17+
<body>
18+
<div id="map"></div>
19+
<script>
20+
const map = L.map('map')
21+
let hash = new L.Hash(map)
22+
if (!window.location.hash) map.setView(new L.LatLng(25.0412,121.5177),16)
23+
let url = 'https://api.protomaps.com/tiles/v2/{z}/{x}/{y}.pbf?key=1003762824b9687f'
24+
25+
const ICONS = `
26+
<html>
27+
<body>
28+
<svg id="hospital" width="32px" height="32px" viewBox="4 4 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="fill-rule:evenodd;clip-rule:evenodd;stroke-miterlimit:10;">
29+
<g transform="matrix(1,0,0,1,-143.475,-174.472)">
30+
<g transform="matrix(1,0,0,1,160.5,179.497)">
31+
<path d="M0,16L-8,16C-10.209,16 -12,14.209 -12,12L-12,4C-12,1.791 -10.209,0 -8,0L0,0C2.209,0 4,1.791 4,4L4,12C4,14.209 2.209,16 0,16Z" style="fill:rgb(239,216,217);fill-rule:nonzero;stroke:rgb(216,30,58);stroke-width:1px;"/>
32+
</g>
33+
<g transform="matrix(1,0,0,1,154.5,185.497)">
34+
<path d="M0,4L-3,4L-3,0L0,0L0,-3L4,-3L4,0L7,0L7,4L4,4L4,7L0,7L0,4Z" style="fill:rgb(234,101,107);fill-rule:nonzero;stroke:white;stroke-width:1px;"/>
35+
</g>
36+
</g>
37+
</svg>
38+
</body>
39+
</html>
40+
`
41+
42+
let sheet = new protomaps.Sheet(ICONS)
43+
44+
var layer = protomaps.leafletLayer({url:url,tasks:[sheet.load()],label_rules:[
45+
{
46+
dataLayer:"pois",
47+
symbolizer:new protomaps.IconSymbolizer({
48+
name:"hospital",
49+
sheet:sheet
50+
})
51+
}
52+
]})
53+
layer.addTo(map)
54+
</script>
55+
</body>
56+
</html>

package-lock.json

+15-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@types/baretest": "^2.0.0",
1414
"@types/mapbox__point-geometry": "^0.1.2",
1515
"@types/mapbox__vector-tile": "^1.3.0",
16+
"@types/css-font-loading-module": "^0.0.7",
1617
"@types/node": "^16.11.1",
1718
"@types/pbf": "^3.0.2",
1819
"@types/rbush": "^3.0.0",
@@ -33,7 +34,6 @@
3334
"pmtiles": "1.0.3",
3435
"polylabel": "^1.1.0",
3536
"potpack": "^1.0.2",
36-
"protosprites": "0.2.0",
3737
"rbush": "^3.0.1"
3838
},
3939
"scripts": {

src/symbolizer.ts

+25-17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Label, Layout } from "./labeler";
1616
import { lineCells, simpleLabel } from "./line";
1717
import { linebreak } from "./text";
1818
import { Bbox, Feature, GeomType } from "./tilecache";
19+
import { Sheet } from "./task";
1920

2021
// https://bugs.webkit.org/show_bug.cgi?id=230751
2122
const MAX_VERTICES_PER_DRAW_CALL = 5400;
@@ -360,41 +361,48 @@ export class LineSymbolizer implements PaintSymbolizer {
360361
}
361362
}
362363

363-
interface Sprite {
364-
canvas: CanvasImageSource;
365-
x: number;
366-
y: number;
367-
w: number;
368-
h: number;
369-
}
370-
371364
export interface IconSymbolizerOptions {
372365
name: string;
373-
sprites?: Map<string, Sprite>;
366+
sheet: Sheet;
374367
}
375368
export class IconSymbolizer implements LabelSymbolizer {
376369
name: string;
377-
sprites: Map<string, Sprite>;
370+
sheet: Sheet;
371+
dpr: number;
378372

379373
constructor(options: IconSymbolizerOptions) {
380374
this.name = options.name;
381-
this.sprites = options.sprites ?? new Map();
375+
this.sheet = options.sheet;
376+
this.dpr = window.devicePixelRatio;
382377
}
383378

384379
public place(layout: Layout, geom: Point[][], feature: Feature) {
385380
let pt = geom[0];
386381
let a = new Point(geom[0][0].x, geom[0][0].y);
382+
let loc = this.sheet.get(this.name);
383+
let width = loc.w / this.dpr;
384+
let height = loc.h / this.dpr;
385+
387386
let bbox = {
388-
minX: a.x - 32,
389-
minY: a.y - 32,
390-
maxX: a.x + 32,
391-
maxY: a.y + 32,
387+
minX: a.x - width / 2,
388+
minY: a.y - height / 2,
389+
maxX: a.x + width / 2,
390+
maxY: a.y + height / 2,
392391
};
393392

394393
let draw = (ctx: CanvasRenderingContext2D) => {
395394
ctx.globalAlpha = 1;
396-
let r = this.sprites.get(this.name);
397-
if (r) ctx.drawImage(r.canvas, r.x, r.y, r.w, r.h, -8, -8, r.w, r.h);
395+
ctx.drawImage(
396+
this.sheet.canvas,
397+
loc.x,
398+
loc.y,
399+
loc.w,
400+
loc.h,
401+
-loc.w / 2 / this.dpr,
402+
-loc.h / 2 / this.dpr,
403+
loc.w / 2,
404+
loc.h / 2
405+
);
398406
};
399407
return [{ anchor: a, bboxes: [bbox], draw: draw }];
400408
}

0 commit comments

Comments
 (0)