protomaps-leaflet
Version:
Vector tile rendering and labeling for [Leaflet](https://github.com/Leaflet/Leaflet).
1 lines • 38.2 kB
Source Map (JSON)
{"version":3,"sources":["/Users/bdon/workspace/protomaps/protomaps-leaflet/dist/cjs/index.cjs","../../src/frontends/static.ts","../../src/default_style/style.ts","../../src/attribute.ts","../../src/task.ts"],"names":["_StringAttr","c","defaultValue","z","f","__name","StringAttr","_NumberAttr","NumberAttr","_TextAttr","options","_a","retval","labelProps","property","transform","word","TextAttr","_FontAttr","_b","style","_Sheet"],"mappings":"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","file":"/Users/bdon/workspace/protomaps/protomaps-leaflet/dist/cjs/index.cjs","sourcesContent":[null,"import Point from \"@mapbox/point-geometry\";\n\nimport { namedFlavor } from \"@protomaps/basemaps\";\nimport { PMTiles } from \"pmtiles\";\nimport { labelRules, paintRules } from \"../default_style/style\";\nimport { LabelRule, Labeler } from \"../labeler\";\nimport { PaintRule, paint } from \"../painter\";\nimport { PreparedTile, SourceOptions, View, sourcesToViews } from \"../view\";\n\nconst R = 6378137;\nconst MAX_LATITUDE = 85.0511287798;\nconst MAXCOORD = R * Math.PI;\n\nconst project = (latlng: Point): Point => {\n const d = Math.PI / 180;\n const constrainedLat = Math.max(\n Math.min(MAX_LATITUDE, latlng.y),\n -MAX_LATITUDE,\n );\n const sin = Math.sin(constrainedLat * d);\n return new Point(R * latlng.x * d, (R * Math.log((1 + sin) / (1 - sin))) / 2);\n};\n\nconst unproject = (point: Point) => {\n const d = 180 / Math.PI;\n return {\n lat: (2 * Math.atan(Math.exp(point.y / R)) - Math.PI / 2) * d,\n lng: (point.x * d) / R,\n };\n};\n\nconst instancedProject = (origin: Point, displayZoom: number) => {\n return (latlng: Point) => {\n const projected = project(latlng);\n const normalized = new Point(\n (projected.x + MAXCOORD) / (MAXCOORD * 2),\n 1 - (projected.y + MAXCOORD) / (MAXCOORD * 2),\n );\n return normalized.mult(2 ** displayZoom * 256).sub(origin);\n };\n};\n\nconst instancedUnproject = (origin: Point, displayZoom: number) => {\n return (point: Point) => {\n const normalized = new Point(point.x, point.y)\n .add(origin)\n .div(2 ** displayZoom * 256);\n const projected = new Point(\n normalized.x * (MAXCOORD * 2) - MAXCOORD,\n (1 - normalized.y) * (MAXCOORD * 2) - MAXCOORD,\n );\n return unproject(projected);\n };\n};\n\nexport const getZoom = (degreesLng: number, cssPixels: number): number => {\n const d = cssPixels * (360 / degreesLng);\n return Math.log2(d / 256);\n};\n\ninterface StaticOptions {\n debug?: string;\n lang?: string;\n maxDataZoom?: number;\n url?: PMTiles | string;\n sources?: Record<string, SourceOptions>;\n paintRules?: PaintRule[];\n labelRules?: LabelRule[];\n backgroundColor?: string;\n flavor?: string;\n}\n\nexport class Static {\n paintRules: PaintRule[];\n labelRules: LabelRule[];\n views: Map<string, View>;\n debug?: string;\n backgroundColor?: string;\n\n constructor(options: StaticOptions) {\n if (options.flavor) {\n const flavor = namedFlavor(options.flavor);\n this.paintRules = paintRules(flavor);\n this.labelRules = labelRules(flavor, options.lang || \"en\");\n this.backgroundColor = flavor.background;\n } else {\n this.paintRules = options.paintRules || [];\n this.labelRules = options.labelRules || [];\n this.backgroundColor = options.backgroundColor;\n }\n\n this.views = sourcesToViews(options);\n this.debug = options.debug || \"\";\n }\n\n async drawContext(\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n latlng: Point,\n displayZoom: number,\n ) {\n const center = project(latlng);\n const normalizedCenter = new Point(\n (center.x + MAXCOORD) / (MAXCOORD * 2),\n 1 - (center.y + MAXCOORD) / (MAXCOORD * 2),\n );\n\n // the origin of the painter call in global Z coordinates\n const origin = normalizedCenter\n .clone()\n .mult(2 ** displayZoom * 256)\n .sub(new Point(width / 2, height / 2));\n\n // the bounds of the painter call in global Z coordinates\n const bbox = {\n minX: origin.x,\n minY: origin.y,\n maxX: origin.x + width,\n maxY: origin.y + height,\n };\n\n const promises = [];\n for (const [k, v] of this.views) {\n const promise = v.getBbox(displayZoom, bbox);\n promises.push({ key: k, promise: promise });\n }\n const tileResponses = await Promise.all(\n promises.map((o) => {\n return o.promise.then(\n (v: PreparedTile[]) => {\n return { status: \"fulfilled\", value: v, key: o.key };\n },\n (error: Error) => {\n return { status: \"rejected\", value: [], reason: error, key: o.key };\n },\n );\n }),\n );\n\n const preparedTilemap = new Map<string, PreparedTile[]>();\n for (const tileResponse of tileResponses) {\n if (tileResponse.status === \"fulfilled\") {\n preparedTilemap.set(tileResponse.key, tileResponse.value);\n }\n }\n\n const start = performance.now();\n const labeler = new Labeler(\n displayZoom,\n ctx,\n this.labelRules,\n 16,\n undefined,\n ); // because need ctx to measure\n\n const layoutTime = labeler.add(preparedTilemap);\n\n if (this.backgroundColor) {\n ctx.save();\n ctx.fillStyle = this.backgroundColor;\n ctx.fillRect(0, 0, width, height);\n ctx.restore();\n }\n\n const paintRules = this.paintRules;\n\n const p = paint(\n ctx,\n displayZoom,\n preparedTilemap,\n labeler.index,\n paintRules,\n bbox,\n origin,\n true,\n this.debug,\n );\n\n if (this.debug) {\n ctx.save();\n ctx.translate(-origin.x, -origin.y);\n ctx.strokeStyle = this.debug;\n ctx.fillStyle = this.debug;\n ctx.font = \"12px sans-serif\";\n let idx = 0;\n for (const [k, v] of preparedTilemap) {\n for (const preparedTile of v) {\n ctx.strokeRect(\n preparedTile.origin.x,\n preparedTile.origin.y,\n preparedTile.dim,\n preparedTile.dim,\n );\n const dt = preparedTile.dataTile;\n ctx.fillText(\n `${k + (k ? \" \" : \"\") + dt.z} ${dt.x} ${dt.y}`,\n preparedTile.origin.x + 4,\n preparedTile.origin.y + 14 * (1 + idx),\n );\n }\n idx++;\n }\n ctx.restore();\n }\n\n // TODO this API isn't so elegant\n return {\n elapsed: performance.now() - start,\n project: instancedProject(origin, displayZoom),\n unproject: instancedUnproject(origin, displayZoom),\n };\n }\n\n async drawCanvas(\n canvas: HTMLCanvasElement,\n latlng: Point,\n displayZoom: number,\n options: StaticOptions = {},\n ) {\n const dpr = window.devicePixelRatio;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n if (!(canvas.width === width * dpr && canvas.height === height * dpr)) {\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n }\n if (options.lang) canvas.lang = options.lang;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n console.error(\"Failed to initialize canvas2d context.\");\n return;\n }\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n return this.drawContext(ctx, width, height, latlng, displayZoom);\n }\n\n async drawContextBounds(\n ctx: CanvasRenderingContext2D,\n topLeft: Point,\n bottomRight: Point,\n width: number,\n height: number,\n ) {\n const deltaDegrees = bottomRight.x - topLeft.x;\n const center = new Point(\n (topLeft.x + bottomRight.x) / 2,\n (topLeft.y + bottomRight.y) / 2,\n );\n return this.drawContext(\n ctx,\n width,\n height,\n center,\n getZoom(deltaDegrees, width),\n );\n }\n\n async drawCanvasBounds(\n canvas: HTMLCanvasElement,\n topLeft: Point,\n bottomRight: Point,\n width: number,\n options: StaticOptions = {},\n ) {\n const deltaDegrees = bottomRight.x - topLeft.x;\n const center = new Point(\n (topLeft.x + bottomRight.x) / 2,\n (topLeft.y + bottomRight.y) / 2,\n );\n return this.drawCanvas(\n canvas,\n center,\n getZoom(deltaDegrees, width),\n options,\n );\n }\n}\n","import { type Flavor } from \"@protomaps/basemaps\";\nimport { mix } from \"color2k\";\nimport { LabelRule } from \"../labeler\";\nimport { PaintRule } from \"../painter\";\nimport {\n CenteredTextSymbolizer,\n CircleSymbolizer,\n GroupSymbolizer,\n LineLabelSymbolizer,\n LineSymbolizer,\n OffsetTextSymbolizer,\n PolygonSymbolizer,\n exp,\n linear,\n} from \"../symbolizer\";\nimport { Feature, GeomType, JsonObject } from \"../tilecache\";\n\nconst getString = (props: JsonObject, key: string): string => {\n const val = props[key];\n if (typeof val === \"string\") return val;\n return \"\";\n};\n\nconst getNumber = (props: JsonObject, key: string): number => {\n const val = props[key];\n if (typeof val === \"number\") return val;\n return 0;\n};\n\nexport const paintRules = (t: Flavor): PaintRule[] => {\n return [\n {\n dataLayer: \"earth\",\n symbolizer: new PolygonSymbolizer({\n fill: t.earth,\n }),\n },\n ...(t.landcover\n ? [\n {\n dataLayer: \"landcover\",\n symbolizer: new PolygonSymbolizer({\n fill: (z, f) => {\n const landcover = t.landcover;\n if (!landcover || !f) return \"\";\n const kind = getString(f.props, \"kind\");\n if (kind === \"grassland\") return landcover.grassland;\n if (kind === \"barren\") return landcover.barren;\n if (kind === \"urban_area\") return landcover.urban_area;\n if (kind === \"farmland\") return landcover.farmland;\n if (kind === \"glacier\") return landcover.glacier;\n if (kind === \"scrub\") return landcover.scrub;\n return landcover.forest;\n },\n opacity: (z, f) => {\n if (z === 8) return 0.5;\n return 1;\n },\n }),\n },\n ]\n : []),\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: (z, f) => {\n return mix(t.park_a, t.park_b, Math.min(Math.max(z / 12.0, 12), 0));\n },\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"allotments\", \"village_green\", \"playground\"].includes(kind);\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.park_b,\n opacity: (z, f) => {\n if (z < 8) return 0;\n if (z === 8) return 0.5;\n return 1;\n },\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\n \"national_park\",\n \"park\",\n \"cemetery\",\n \"protected_area\",\n \"nature_reserve\",\n \"forest\",\n \"golf_course\",\n ].includes(kind);\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.hospital,\n }),\n filter: (z, f) => {\n return f.props.kind === \"hospital\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.industrial,\n }),\n filter: (z, f) => {\n return f.props.kind === \"industrial\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.school,\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"school\", \"university\", \"college\"].includes(kind);\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.beach,\n }),\n filter: (z, f) => {\n return f.props.kind === \"beach\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.zoo,\n }),\n filter: (z, f) => {\n return f.props.kind === \"zoo\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.zoo,\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"military\", \"naval_base\", \"airfield\"].includes(kind);\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: (z, f) => {\n return mix(t.wood_a, t.wood_b, Math.min(Math.max(z / 12.0, 12), 0));\n },\n opacity: (z, f) => {\n if (z < 8) return 0;\n if (z === 8) return 0.5;\n return 1;\n },\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"wood\", \"nature_reserve\", \"forest\"].includes(kind);\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: (z, f) => {\n return mix(t.scrub_a, t.scrub_b, Math.min(Math.max(z / 12.0, 12), 0));\n },\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"scrub\", \"grassland\", \"grass\"].includes(kind);\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.scrub_b,\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"scrub\", \"grassland\", \"grass\"].includes(kind);\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.glacier,\n }),\n filter: (z, f) => {\n return f.props.kind === \"glacier\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.sand,\n opacity: (z, f) => {\n if (z < 8) return 0;\n if (z === 8) return 0.5;\n return 1;\n },\n }),\n filter: (z, f) => {\n return f.props.kind === \"sand\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.aerodrome,\n }),\n filter: (z, f) => {\n return f.props.kind === \"aerodrome\";\n },\n },\n {\n dataLayer: \"water\",\n symbolizer: new PolygonSymbolizer({\n fill: t.water,\n }),\n filter: (z, f) => {\n return f.geomType === GeomType.Polygon;\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n color: t.runway,\n width: (z, f) => {\n return exp(1.6, [\n [11, 0],\n [13, 4],\n [19, 30],\n ])(z);\n },\n }),\n filter: (z, f) => {\n return f.props.kind_detail === \"runway\";\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n color: t.runway,\n width: (z, f) => {\n return exp(1.6, [\n [14, 0],\n [14.5, 1],\n [16, 6],\n ])(z);\n },\n }),\n filter: (z, f) => {\n return f.props.kind_detail === \"taxiway\";\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n color: t.pier,\n width: (z, f) => {\n return exp(1.6, [\n [13, 0],\n [13.5, 0, 5],\n [21, 16],\n ])(z);\n },\n }),\n filter: (z, f) => {\n return f.props.kind === \"path\" && f.props.kind_detail === \"pier\";\n },\n },\n {\n dataLayer: \"water\",\n minzoom: 14,\n symbolizer: new LineSymbolizer({\n color: t.water,\n width: (z, f) => {\n return exp(1.6, [\n [9, 0],\n [9.5, 1.0],\n [18, 12],\n ])(z);\n },\n }),\n filter: (z, f) => {\n return f.geomType === GeomType.Line && f.props.kind === \"river\";\n },\n },\n {\n dataLayer: \"water\",\n minzoom: 14,\n symbolizer: new LineSymbolizer({\n color: t.water,\n width: 0.5,\n }),\n filter: (z, f) => {\n return f.geomType === GeomType.Line && f.props.kind === \"stream\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.pedestrian,\n }),\n filter: (z, f) => {\n return f.props.kind === \"pedestrian\";\n },\n },\n {\n dataLayer: \"landuse\",\n symbolizer: new PolygonSymbolizer({\n fill: t.pier,\n }),\n filter: (z, f) => {\n return f.props.kind === \"pier\";\n },\n },\n {\n dataLayer: \"buildings\",\n symbolizer: new PolygonSymbolizer({\n fill: t.buildings,\n opacity: 0.5,\n }),\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n color: t.major,\n width: (z, f) => {\n return exp(1.6, [\n [14, 0],\n [20, 7],\n ])(z);\n },\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"other\", \"path\"].includes(kind);\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n color: t.major,\n width: (z, f) => {\n return exp(1.6, [\n [13, 0],\n [18, 8],\n ])(z);\n },\n }),\n filter: (z, f) => {\n return f.props.kind === \"minor_road\";\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n color: t.major,\n width: (z, f) => {\n return exp(1.6, [\n [6, 0],\n [12, 1.6],\n [15, 3],\n [18, 13],\n ])(z);\n },\n }),\n filter: (z, f) => {\n return f.props.kind === \"major_road\";\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n color: t.major,\n width: (z, f) => {\n return exp(1.6, [\n [3, 0],\n [6, 1.1],\n [12, 1.6],\n [15, 5],\n [18, 15],\n ])(z);\n },\n }),\n filter: (z, f) => {\n return f.props.kind === \"highway\";\n },\n },\n {\n dataLayer: \"boundaries\",\n symbolizer: new LineSymbolizer({\n color: t.boundaries,\n width: 1,\n }),\n filter: (z, f) => {\n const minAdminLevel = f.props.kind_detail;\n return typeof minAdminLevel === \"number\" && minAdminLevel <= 2;\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineSymbolizer({\n dash: [0.3, 0.75],\n color: t.railway,\n dashWidth: (z, f) => {\n return exp(1.6, [\n [4, 0],\n [7, 0.15],\n [19, 9],\n ])(z);\n },\n opacity: 0.5,\n }),\n filter: (z, f) => {\n return f.props.kind === \"rail\";\n },\n },\n {\n dataLayer: \"boundaries\",\n symbolizer: new LineSymbolizer({\n color: t.boundaries,\n width: 0.5,\n }),\n filter: (z, f) => {\n const minAdminLevel = f.props.kind_detail;\n return typeof minAdminLevel === \"number\" && minAdminLevel > 2;\n },\n },\n ];\n};\n\nexport const labelRules = (t: Flavor, lang: string): LabelRule[] => {\n const nametags = [`name:${lang}`, \"name\"];\n\n return [\n // {\n // id: \"neighbourhood\",\n // dataLayer: \"places\",\n // symbolizer: languageStack(\n // new CenteredTextSymbolizer({\n // labelProps: nametags,\n // fill: params.neighbourhoodLabel,\n // font: \"500 10px sans-serif\",\n // textTransform: \"uppercase\",\n // }),\n // params.neighbourhoodLabel,\n // ),\n // filter: (z, f) => {\n // return f.props[\"kind\"] === \"neighbourhood\";\n // },\n // },\n {\n dataLayer: \"roads\",\n symbolizer: new LineLabelSymbolizer({\n labelProps: nametags,\n fill: t.roads_label_minor,\n font: \"400 12px sans-serif\",\n width: 2,\n stroke: t.roads_label_minor_halo,\n }),\n // TODO: sort by minzoom\n minzoom: 16,\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"minor_road\", \"other\", \"path\"].includes(kind);\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineLabelSymbolizer({\n labelProps: nametags,\n fill: t.roads_label_major,\n font: \"400 12px sans-serif\",\n width: 2,\n stroke: t.roads_label_major_halo,\n }),\n // TODO: sort by minzoom\n minzoom: 12,\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"highway\", \"major_road\"].includes(kind);\n },\n },\n {\n dataLayer: \"roads\",\n symbolizer: new LineLabelSymbolizer({\n labelProps: nametags,\n fill: t.roads_label_major,\n font: \"400 12px sans-serif\",\n width: 2,\n stroke: t.roads_label_major_halo,\n }),\n // TODO: sort by minzoom\n minzoom: 12,\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return [\"highway\", \"major_road\"].includes(kind);\n },\n },\n {\n dataLayer: \"water\",\n symbolizer: new CenteredTextSymbolizer({\n labelProps: nametags,\n fill: t.ocean_label,\n lineHeight: 1.5,\n letterSpacing: 1,\n font: (z, f) => {\n const size = linear([\n [3, 10],\n [10, 12],\n ])(z);\n return `400 ${size}px sans-serif`;\n },\n textTransform: \"uppercase\",\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return (\n f.geomType === GeomType.Point &&\n [\"ocean\", \"bay\", \"strait\", \"fjord\"].includes(kind)\n );\n },\n },\n {\n dataLayer: \"water\",\n symbolizer: new CenteredTextSymbolizer({\n labelProps: nametags,\n fill: t.ocean_label,\n lineHeight: 1.5,\n letterSpacing: 1,\n font: (z, f) => {\n const size = linear([\n [3, 10],\n [6, 12],\n [10, 12],\n ])(z);\n return `400 ${size}px sans-serif`;\n },\n }),\n filter: (z, f) => {\n const kind = getString(f.props, \"kind\");\n return (\n f.geomType === GeomType.Point &&\n [\"sea\", \"lake\", \"water\"].includes(kind)\n );\n },\n },\n {\n dataLayer: \"places\",\n symbolizer: new CenteredTextSymbolizer({\n labelProps: (z, f) => {\n if (z < 6) {\n return [`ref:${lang}`, \"ref\"];\n }\n return nametags;\n },\n fill: t.state_label,\n stroke: t.state_label_halo,\n width: 1,\n lineHeight: 1.5,\n font: (z: number, f?: Feature) => {\n return \"400 12px sans-serif\";\n },\n textTransform: \"uppercase\",\n }),\n filter: (z, f) => {\n return f.props.kind === \"region\";\n },\n },\n {\n dataLayer: \"places\",\n symbolizer: new CenteredTextSymbolizer({\n labelProps: nametags,\n fill: t.country_label,\n lineHeight: 1.5,\n font: (z: number, f?: Feature) => {\n if (z < 6) return \"600 12px sans-serif\";\n return \"600 12px sans-serif\";\n },\n textTransform: \"uppercase\",\n }),\n filter: (z, f) => {\n return f.props.kind === \"country\";\n },\n },\n {\n // places_locality\n dataLayer: \"places\",\n minzoom: 9,\n symbolizer: new CenteredTextSymbolizer({\n labelProps: nametags,\n fill: t.city_label,\n lineHeight: 1.5,\n font: (z: number, f?: Feature) => {\n if (!f) return \"400 12px sans-serif\";\n const minZoom = f.props.min_zoom;\n let weight = 400;\n if (minZoom && minZoom <= 5) {\n weight = 600;\n }\n let size = 12;\n const popRank = f.props.population_rank;\n if (popRank && popRank > 9) {\n size = 16;\n }\n return `${weight} ${size}px sans-serif`;\n },\n }),\n sort: (a, b) => {\n const aRank = getNumber(a, \"min_zoom\");\n const bRank = getNumber(b, \"min_zoom\");\n return aRank - bRank;\n },\n filter: (z, f) => {\n return f.props.kind === \"locality\";\n },\n },\n {\n dataLayer: \"places\",\n maxzoom: 8,\n symbolizer: new GroupSymbolizer([\n new CircleSymbolizer({\n radius: 2,\n fill: t.city_label,\n stroke: t.city_label_halo,\n width: 1.5,\n }),\n new OffsetTextSymbolizer({\n labelProps: nametags,\n fill: t.city_label,\n stroke: t.city_label_halo,\n width: 1,\n offsetX: 6,\n offsetY: 4.5,\n font: (z, f) => {\n return \"400 12px sans-serif\";\n },\n }),\n ]),\n filter: (z, f) => {\n return f.props.kind === \"locality\";\n },\n },\n ];\n};\n","import { Feature } from \"./tilecache\";\n\nexport type AttrOption<T> = T | ((z: number, f?: Feature) => T);\n\nexport class StringAttr<T extends string = string> {\n str: AttrOption<T>;\n perFeature: boolean;\n\n constructor(c: AttrOption<T> | undefined, defaultValue: T) {\n this.str = c ?? defaultValue;\n this.perFeature = typeof this.str === \"function\" && this.str.length === 2;\n }\n\n public get(z: number, f?: Feature): T {\n if (typeof this.str === \"function\") {\n return this.str(z, f);\n }\n return this.str;\n }\n}\n\nexport class NumberAttr {\n value: AttrOption<number>;\n perFeature: boolean;\n\n constructor(c: AttrOption<number> | undefined, defaultValue = 1) {\n this.value = c ?? defaultValue;\n this.perFeature =\n typeof this.value === \"function\" && this.value.length === 2;\n }\n\n public get(z: number, f?: Feature): number {\n if (typeof this.value === \"function\") {\n return this.value(z, f);\n }\n return this.value;\n }\n}\n\nexport interface TextAttrOptions {\n labelProps?: AttrOption<string[]>;\n textTransform?: AttrOption<string>;\n}\n\nexport class TextAttr {\n labelProps: AttrOption<string[]>;\n textTransform?: AttrOption<string>;\n\n constructor(options?: TextAttrOptions) {\n this.labelProps = options?.labelProps ?? [\"name\"];\n this.textTransform = options?.textTransform;\n }\n\n public get(z: number, f: Feature): string | undefined {\n let retval: string | undefined;\n\n let labelProps: string[];\n if (typeof this.labelProps === \"function\") {\n labelProps = this.labelProps(z, f);\n } else {\n labelProps = this.labelProps;\n }\n for (const property of labelProps) {\n if (\n Object.prototype.hasOwnProperty.call(f.props, property) &&\n typeof f.props[property] === \"string\"\n ) {\n retval = f.props[property] as string;\n break;\n }\n }\n let transform: string | ((z: number, f: Feature) => string) | undefined;\n if (typeof this.textTransform === \"function\") {\n transform = this.textTransform(z, f);\n } else {\n transform = this.textTransform;\n }\n if (retval && transform === \"uppercase\") retval = retval.toUpperCase();\n else if (retval && transform === \"lowercase\") retval = retval.toLowerCase();\n else if (retval && transform === \"capitalize\") {\n const wordsArray = retval.toLowerCase().split(\" \");\n const capsArray = wordsArray.map((word: string) => {\n return word[0].toUpperCase() + word.slice(1);\n });\n retval = capsArray.join(\" \");\n }\n return retval;\n }\n}\n\nexport interface FontAttrOptions {\n font?: AttrOption<string>;\n fontFamily?: AttrOption<string>;\n fontSize?: AttrOption<number>;\n fontWeight?: AttrOption<number>;\n fontStyle?: AttrOption<string>;\n}\n\nexport class FontAttr {\n family?: AttrOption<string>;\n size?: AttrOption<number>;\n weight?: AttrOption<number>;\n style?: AttrOption<string>;\n font?: AttrOption<string>;\n\n constructor(options?: FontAttrOptions) {\n if (options?.font) {\n this.font = options.font;\n } else {\n this.family = options?.fontFamily ?? \"sans-serif\";\n this.size = options?.fontSize ?? 12;\n this.weight = options?.fontWeight;\n this.style = options?.fontStyle;\n }\n }\n\n public get(z: number, f?: Feature) {\n if (this.font) {\n if (typeof this.font === \"function\") {\n return this.font(z, f);\n }\n return this.font;\n }\n let style = \"\";\n if (this.style) {\n if (typeof this.style === \"function\") {\n style = `${this.style(z, f)} `;\n } else {\n style = `${this.style} `;\n }\n }\n\n let weight = \"\";\n if (this.weight) {\n if (typeof this.weight === \"function\") {\n weight = `${this.weight(z, f)} `;\n } else {\n weight = `${this.weight} `;\n }\n }\n\n let size: number | ((z: number, f: Feature) => number) | undefined;\n if (typeof this.size === \"function\") {\n size = this.size(z, f);\n } else {\n size = this.size;\n }\n\n let family: string | ((z: number, f: Feature) => string) | undefined;\n if (typeof this.family === \"function\") {\n family = this.family(z, f);\n } else {\n family = this.family;\n }\n\n return `${style}${weight}${size}px ${family}`;\n }\n}\n\nexport class ArrayAttr<T = number> {\n value: AttrOption<T[]>;\n perFeature: boolean;\n\n constructor(c: AttrOption<T[]>, defaultValue: T[] = []) {\n this.value = c ?? defaultValue;\n this.perFeature =\n typeof this.value === \"function\" && this.value.length === 2;\n }\n\n public get(z: number, f?: Feature): T[] {\n if (typeof this.value === \"function\") {\n return this.value(z, f);\n }\n return this.value;\n }\n}\n","import potpack from \"potpack\";\n\n// https://github.com/tangrams/tangram/blob/master/src/styles/text/font_manager.js\nexport const Font = (name: string, url: string, weight: string) => {\n const ff = new FontFace(name, `url(${url})`, { weight: weight });\n document.fonts.add(ff);\n return ff.load();\n};\n\ninterface Sprite {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\ninterface PotPackInput {\n x?: number;\n y?: number;\n w: number;\n h: number;\n id: string;\n img: HTMLImageElement;\n}\n\nconst mkimg = async (src: string): Promise<HTMLImageElement> => {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => reject(\"Invalid SVG\");\n img.src = src;\n });\n};\n\nconst MISSING = `\n<svg width=\"20px\" height=\"20px\" viewBox=\"0 0 50 50\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"50\" height=\"50\" fill=\"#cccccc\"/>\n <g transform=\"translate(5,5)\">\n <path fill=\"none\" stroke=\"#666666\" stroke-width=\"7\" d=\"m11,12a8.5,8 0 1,1 17,0q0,4-4,6t-4.5,4.5-.4,4v.2m0,3v7\"/>\n </g>\n</svg>\n`;\n\nexport class Sheet {\n src: string;\n canvas: HTMLCanvasElement;\n mapping: Map<string, Sprite>;\n missingBox: Sprite;\n\n constructor(src: string) {\n this.src = src;\n this.canvas = document.createElement(\"canvas\");\n this.mapping = new Map<string, Sprite>();\n this.missingBox = { x: 0, y: 0, w: 0, h: 0 };\n }\n\n async load() {\n let src = this.src;\n const scale = window.devicePixelRatio;\n if (src.endsWith(\".html\")) {\n const c = await fetch(src);\n src = await c.text();\n }\n const tree = new window.DOMParser().parseFromString(src, \"text/html\");\n const icons = Array.from(tree.body.children);\n\n const missingImg = await mkimg(\n `data:image/svg+xml;base64,${btoa(MISSING)}`,\n );\n\n const boxes: PotPackInput[] = [\n {\n w: missingImg.width * scale,\n h: missingImg.height * scale,\n img: missingImg,\n id: \"\",\n },\n ];\n\n const serializer = new XMLSerializer();\n for (const ps of icons) {\n const svg64 = btoa(serializer.serializeToString(ps));\n const image64 = `data:image/svg+xml;base64,${svg64}`;\n const img = await mkimg(image64);\n boxes.push({\n w: img.width * scale,\n h: img.height * scale,\n img: img,\n id: ps.id,\n });\n }\n\n const packresult = potpack(boxes);\n this.canvas.width = packresult.w;\n this.canvas.height = packresult.h;\n const ctx = this.canvas.getContext(\"2d\");\n if (ctx) {\n for (const box of boxes) {\n if (box.x !== undefined && box.y !== undefined) {\n ctx.drawImage(box.img, box.x, box.y, box.w, box.h);\n if (box.id)\n this.mapping.set(box.id, {\n x: box.x,\n y: box.y,\n w: box.w,\n h: box.h,\n });\n else this.missingBox = { x: box.x, y: box.y, w: box.w, h: box.h };\n }\n }\n }\n return this;\n }\n\n get(name: string): Sprite {\n let result = this.mapping.get(name);\n if (!result) result = this.missingBox;\n return result;\n }\n}\n"]}