1
2
3
4
5
6
7
8
9
10
11import { MapView } from "mapgpu";
12import { GeoJSONLayer, RasterTileLayer } from "mapgpu/layers";
13import { RenderEngine } from "mapgpu/render";
14import { UniqueValueRenderer } from "mapgpu/core";
15
16import type { RunResultObject } from "@/components/examples/ExampleCanvas";
17
18type Category = "capital" | "port" | "airport";
19
20interface CityProps {
21 name: string;
22 category: Category;
23}
24
25const DATA = {
26 type: "FeatureCollection" as const,
27 features: [
28 { type: "Feature" as const, properties: { name: "Ankara", category: "capital" }, geometry: { type: "Point" as const, coordinates: [32.866, 39.925] } },
29 { type: "Feature" as const, properties: { name: "Istanbul", category: "port" }, geometry: { type: "Point" as const, coordinates: [28.979, 41.015] } },
30 { type: "Feature" as const, properties: { name: "Izmir", category: "port" }, geometry: { type: "Point" as const, coordinates: [27.140, 38.423] } },
31 { type: "Feature" as const, properties: { name: "Antalya", category: "airport" }, geometry: { type: "Point" as const, coordinates: [30.714, 36.897] } },
32 { type: "Feature" as const, properties: { name: "Trabzon", category: "port" }, geometry: { type: "Point" as const, coordinates: [39.727, 41.005] } },
33 { type: "Feature" as const, properties: { name: "Gaziantep", category: "airport" }, geometry: { type: "Point" as const, coordinates: [37.378, 37.067] } },
34 ],
35};
36
37const SIZE = 128;
38
39function svg(path: string, fill: string): string {
40 return `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 24 24">
41 <circle cx="12" cy="12" r="11" fill="${fill}" stroke="white" stroke-width="1.5"/>
42 <path d="${path}" fill="white"/>
43 </svg>`;
44}
45
46const ICONS: Record<Category, string> = {
47 capital: svg("M12 6l2.5 5 5.5 0.8-4 3.9 1 5.3-5-2.6-5 2.6 1-5.3-4-3.9 5.5-0.8z", "#ff5c1a"),
48 port: svg("M12 4l1 4h3l-2.5 2.5L15 14l-3-2-3 2 1.5-3.5L8 8h3z", "#43b0ff"),
49 airport: svg(
50 "M21 16l-7-5V5a2 2 0 0 0-4 0v6l-7 5v2l7-2v5l-2 1v1l3-1 3 1v-1l-2-1v-5l7 2z",
51 "#78dc78",
52 ),
53};
54
55
56
57
58async function svgToBitmap(svgSource: string): Promise<ImageBitmap> {
59 const dataUrl =
60 "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgSource);
61 const img = new Image();
62 img.decoding = "async";
63 img.src = dataUrl;
64 await img.decode();
65
66 const canvas = document.createElement("canvas");
67 canvas.width = SIZE;
68 canvas.height = SIZE;
69 const ctx = canvas.getContext("2d");
70 if (!ctx) throw new Error("Canvas 2D context unavailable");
71 ctx.drawImage(img, 0, 0, SIZE, SIZE);
72 return await createImageBitmap(canvas);
73}
74
75export async function run(container: HTMLElement): Promise<RunResultObject> {
76 const view = new MapView({
77 container,
78 renderEngine: new RenderEngine(),
79 mode: "2d",
80 center: [33.0, 39.0],
81 zoom: 5,
82 minZoom: 2,
83 maxZoom: 18,
84 backgroundColor: "transparent",
85 });
86
87 view.map.add(
88 new RasterTileLayer({
89 id: "carto-light",
90 urlTemplate: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
91 subdomains: ["a", "b", "c", "d"],
92 maxZoom: 19,
93 attribution: "© CARTO · © OpenStreetMap contributors",
94 }),
95 );
96
97
98
99 await view.when();
100
101
102 for (const [id, source] of Object.entries(ICONS) as [Category, string][]) {
103 const bitmap = await svgToBitmap(source);
104 await view.loadIcon(id, bitmap);
105 }
106
107 const cities = new GeoJSONLayer({ id: "cities", data: DATA });
108 cities.renderer = new UniqueValueRenderer({
109 field: "category",
110 defaultSymbol: {
111 type: "icon",
112 src: "capital",
113 color: [255, 255, 255, 255],
114 size: 32,
115 },
116 uniqueValues: (Object.keys(ICONS) as Category[]).map((id) => ({
117 value: id,
118 symbol: {
119 type: "icon",
120 src: id,
121 color: [255, 255, 255, 255],
122 size: 32,
123 },
124 })),
125 });
126 view.map.add(cities);
127
128 return {
129 dispose: () => view.destroy(),
130 controls: [],
131 };
132}