@kahwee/sf-map-svg 0.3.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +44 -3
  2. package/LICENSE +23 -4
  3. package/README.md +267 -74
  4. package/SOURCES.md +4 -0
  5. package/{data → dist/data}/README.md +2 -1
  6. package/dist/data/bart-stations.json +134 -0
  7. package/dist/data/catalog.json +4718 -0
  8. package/dist/data/coast.json +36 -0
  9. package/dist/data/districts-2002.json +325 -0
  10. package/dist/data/districts-2012.json +325 -0
  11. package/dist/data/districts-2022.json +325 -0
  12. package/dist/data/highways.json +4138 -0
  13. package/dist/data/index.d.ts +26 -0
  14. package/{data → dist/data}/index.js +37 -39
  15. package/dist/data/key-roads.json +12662 -0
  16. package/dist/data/landmarks.json +167 -0
  17. package/dist/data/neighborhoods-analysis.json +817 -0
  18. package/dist/data/neighborhoods-realtor.json +1978 -0
  19. package/dist/data/neighborhoods.json +2267 -0
  20. package/dist/data/types.d.ts +117 -0
  21. package/dist/data/types.js +1 -0
  22. package/dist/src/data.d.ts +25 -0
  23. package/{src → dist/src}/data.js +17 -20
  24. package/dist/src/explorer-layout.d.ts +23 -0
  25. package/dist/src/explorer-layout.js +72 -0
  26. package/dist/src/explorer.d.ts +4 -0
  27. package/dist/src/explorer.js +961 -0
  28. package/dist/src/geometry.d.ts +5 -0
  29. package/dist/src/geometry.js +53 -0
  30. package/dist/src/immutable.d.ts +2 -0
  31. package/dist/src/immutable.js +9 -0
  32. package/dist/src/index.d.ts +13 -0
  33. package/dist/src/index.js +125 -0
  34. package/dist/src/interactive.d.ts +4 -0
  35. package/dist/src/interactive.js +5 -0
  36. package/dist/src/layers.d.ts +36 -0
  37. package/dist/src/layers.js +64 -0
  38. package/dist/src/navigation.d.ts +6 -0
  39. package/dist/src/navigation.js +145 -0
  40. package/dist/src/overlays.d.ts +25 -0
  41. package/dist/src/overlays.js +15 -0
  42. package/dist/src/svg.d.ts +4 -0
  43. package/dist/src/svg.js +7 -0
  44. package/dist/src/transit.d.ts +3 -0
  45. package/dist/src/transit.js +105 -0
  46. package/dist/src/types.d.ts +112 -0
  47. package/dist/src/types.js +1 -0
  48. package/dist/src/viewport.d.ts +5 -0
  49. package/dist/src/viewport.js +27 -0
  50. package/package.json +40 -25
  51. package/data/bart-stations.json +0 -134
  52. package/data/catalog.json +0 -4699
  53. package/data/coast.json +0 -36
  54. package/data/districts-2002.json +0 -325
  55. package/data/districts-2012.json +0 -325
  56. package/data/districts-2022.json +0 -325
  57. package/data/highways.json +0 -4138
  58. package/data/index.d.ts +0 -117
  59. package/data/landmarks.json +0 -167
  60. package/data/neighborhoods-analysis.json +0 -817
  61. package/data/neighborhoods-realtor.json +0 -1978
  62. package/data/neighborhoods.json +0 -2267
  63. package/src/geometry.d.ts +0 -8
  64. package/src/geometry.js +0 -51
  65. package/src/immutable.js +0 -8
  66. package/src/index.d.ts +0 -51
  67. package/src/index.js +0 -122
  68. package/src/layers.js +0 -54
  69. package/src/overlays.js +0 -18
  70. package/src/svg.js +0 -12
@@ -0,0 +1,112 @@
1
+ import type { Geometry, NeighborhoodFeature, NeighborhoodSource } from '../data/types.js';
2
+ export type DistrictYear = 2002 | 2012 | 2022;
3
+ export interface MapMarker {
4
+ id: string;
5
+ lng: number;
6
+ lat: number;
7
+ label?: string;
8
+ selected?: boolean;
9
+ color?: string;
10
+ }
11
+ export interface SFMapOptions {
12
+ theme?: 'districts' | 'transit';
13
+ width?: number;
14
+ height?: number;
15
+ padding?: number;
16
+ year?: DistrictYear;
17
+ districtLines?: boolean;
18
+ neighborhoodLines?: boolean;
19
+ districtFills?: boolean;
20
+ districtLabels?: boolean;
21
+ /** Hide all visible text labels while retaining geographic symbols and accessible titles. */
22
+ labels?: boolean;
23
+ highways?: boolean;
24
+ keyRoads?: boolean;
25
+ landmarks?: boolean;
26
+ bartStations?: boolean;
27
+ markers?: MapMarker[];
28
+ title?: string;
29
+ idPrefix?: string;
30
+ colors?: Partial<Record<'water' | 'land' | 'district' | 'neighborhood' | 'highway' | 'road' | 'park' | 'landmark' | 'bart' | 'label' | 'marker' | 'selected', string>>;
31
+ }
32
+ /** Serializable viewport in the interactive map's fixed 800 × 800 projected space. */
33
+ export type MapViewport = readonly [x: number, y: number, size: number];
34
+ export type ExplorerMode = 'neighborhoods' | 'districts' | 'basemap';
35
+ export interface MapPadding {
36
+ top?: number;
37
+ right?: number;
38
+ bottom?: number;
39
+ left?: number;
40
+ }
41
+ export interface InteractiveLayers {
42
+ districtFills?: boolean;
43
+ districtLines?: boolean;
44
+ districtLabels?: boolean;
45
+ neighborhoodLines?: boolean;
46
+ neighborhoodLabels?: boolean;
47
+ landmarks?: boolean;
48
+ bartStations?: boolean;
49
+ highways?: boolean;
50
+ keyRoads?: boolean;
51
+ }
52
+ export interface NeighborhoodSelection {
53
+ id: string;
54
+ name: string;
55
+ source: NeighborhoodSource;
56
+ feature: NeighborhoodFeature;
57
+ }
58
+ export interface NeighborhoodExplorerOptions {
59
+ mode?: ExplorerMode;
60
+ labels?: boolean;
61
+ source?: NeighborhoodSource;
62
+ neighborhood?: string;
63
+ year?: DistrictYear;
64
+ theme?: SFMapOptions['theme'];
65
+ /** Explorer chrome or the reusable map with only controls and attribution. */
66
+ interface?: 'explorer' | 'map';
67
+ /** Independent overrides; omitted layers follow mode defaults. */
68
+ layers?: InteractiveLayers;
69
+ selectableNeighborhoods?: boolean;
70
+ /** Screen pixels; defaults preserve 11px roads / 12px other labels. */
71
+ labelSize?: {
72
+ min?: number;
73
+ max?: number;
74
+ };
75
+ /** Screen-pixel space reserved when fitting geometry. */
76
+ fitPadding?: number | MapPadding;
77
+ markers?: readonly MapMarker[];
78
+ /** Screen pixels, independent of zoom. */
79
+ markerRadius?: number;
80
+ markerHitSize?: number;
81
+ markerColor?: string;
82
+ selectedMarkerColor?: string;
83
+ onMarkerActivate?: (marker: MapMarker) => void;
84
+ }
85
+ export interface NeighborhoodExplorerElement extends HTMLElement {
86
+ selectNeighborhood(name: string | null, options?: {
87
+ fit?: boolean;
88
+ }): boolean;
89
+ getSelection(): NeighborhoodSelection | null;
90
+ setSource(source: NeighborhoodSource): void;
91
+ setMode(mode: ExplorerMode): void;
92
+ setLabels(visible: boolean): void;
93
+ resetView(): void;
94
+ zoomBy(factor: number): void;
95
+ panBy(x: number, y: number): void;
96
+ getViewport(): MapViewport;
97
+ setViewport(view: MapViewport): void;
98
+ /** Fit WGS84 geometry, including Point, MultiPoint, or a GeometryCollection. */
99
+ fitGeometry(geometry: Geometry, padding?: number | MapPadding): void;
100
+ /** Explicitly engage map touch gestures; false restores page gestures. */
101
+ setTouchNavigation(enabled: boolean): void;
102
+ setMarkers(markers: readonly MapMarker[]): void;
103
+ selectMarker(id: string | null, options?: {
104
+ fit?: boolean;
105
+ }): boolean;
106
+ getSelectedMarker(): MapMarker | null;
107
+ destroy(): void;
108
+ }
109
+ /** Paused-by-default schematic BART animation with keyboard-operable controls. */
110
+ export interface TransitAnimationElement extends HTMLElement {
111
+ destroy(): void;
112
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { Bounds } from '../data/types.js';
2
+ import type { MapPadding, MapViewport } from './types.js';
3
+ export declare function validateViewport(view: MapViewport): import("./explorer-layout.js").View;
4
+ /** Fit bounds in the unobscured screen area, then constrain to the city extent. */
5
+ export declare function fitViewport(bounds: Bounds, width: number, padding?: number | MapPadding): import("./explorer-layout.js").View;
@@ -0,0 +1,27 @@
1
+ import { clampView } from './explorer-layout.js';
2
+ export function validateViewport(view) {
3
+ if (view.length !== 3 || !view.every(Number.isFinite) || view[2] <= 0)
4
+ throw new RangeError('Viewport must contain finite x, y, and a positive size.');
5
+ return clampView([...view]);
6
+ }
7
+ /** Fit bounds in the unobscured screen area, then constrain to the city extent. */
8
+ export function fitViewport(bounds, width, padding = 24) {
9
+ const p = typeof padding === 'number'
10
+ ? { top: padding, right: padding, bottom: padding, left: padding }
11
+ : padding;
12
+ const { top = 0, right = 0, bottom = 0, left = 0 } = p;
13
+ if (![width, top, right, bottom, left, ...bounds].every(Number.isFinite) ||
14
+ Math.min(top, right, bottom, left) < 0 ||
15
+ width <= left + right ||
16
+ width <= top + bottom ||
17
+ bounds[0] > bounds[2] ||
18
+ bounds[1] > bounds[3])
19
+ throw new RangeError('Fit requires nonempty finite geometry and padding smaller than the viewport.');
20
+ const size = Math.max(800 / 12, Math.min(800, ((bounds[2] - bounds[0]) * width) / (width - left - right)));
21
+ const fittedSize = Math.max(size, Math.min(800, ((bounds[3] - bounds[1]) * width) / (width - top - bottom)));
22
+ return clampView([
23
+ (bounds[0] + bounds[2]) / 2 - fittedSize / 2 - ((left - right) * fittedSize) / width / 2,
24
+ (bounds[1] + bounds[3]) / 2 - fittedSize / 2 - ((top - bottom) * fittedSize) / width / 2,
25
+ fittedSize,
26
+ ]);
27
+ }
package/package.json CHANGED
@@ -1,52 +1,63 @@
1
1
  {
2
2
  "name": "@kahwee/sf-map-svg",
3
- "version": "0.3.0",
3
+ "version": "1.1.0",
4
4
  "description": "Offline SVG maps of San Francisco with district boundaries, parks, landmarks, and BART stations.",
5
5
  "type": "module",
6
- "main": "./src/index.js",
7
- "types": "./src/index.d.ts",
6
+ "main": "./dist/src/index.js",
7
+ "types": "./dist/src/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./src/index.d.ts",
11
- "import": "./src/index.js"
10
+ "types": "./dist/src/index.d.ts",
11
+ "import": "./dist/src/index.js"
12
12
  },
13
13
  "./data": {
14
- "types": "./data/index.d.ts",
15
- "import": "./data/index.js"
14
+ "types": "./dist/data/index.d.ts",
15
+ "import": "./dist/data/index.js"
16
16
  },
17
- "./data/*.json": "./data/*.json",
17
+ "./data/*.json": "./dist/data/*.json",
18
18
  "./geometry": {
19
- "types": "./src/geometry.d.ts",
20
- "import": "./src/geometry.js"
19
+ "types": "./dist/src/geometry.d.ts",
20
+ "import": "./dist/src/geometry.js"
21
+ },
22
+ "./explorer": {
23
+ "types": "./dist/src/explorer.d.ts",
24
+ "import": "./dist/src/explorer.js"
25
+ },
26
+ "./interactive": {
27
+ "types": "./dist/src/interactive.d.ts",
28
+ "import": "./dist/src/interactive.js"
29
+ },
30
+ "./transit": {
31
+ "types": "./dist/src/transit.d.ts",
32
+ "import": "./dist/src/transit.js"
21
33
  }
22
34
  },
23
35
  "files": [
24
- "src",
36
+ "dist",
25
37
  "README.md",
26
38
  "SOURCES.md",
27
- "LICENSE",
28
- "data"
39
+ "LICENSE"
29
40
  ],
30
41
  "sideEffects": false,
31
42
  "repository": {
32
43
  "type": "git",
33
44
  "url": "git+https://github.com/kahwee/sf-map-svg.git"
34
45
  },
35
- "license": "UNLICENSED",
46
+ "license": "MIT",
36
47
  "engines": {
37
48
  "node": ">=22.12.0"
38
49
  },
39
50
  "devDependencies": {
51
+ "@biomejs/biome": "2.5.14",
40
52
  "@storybook/addon-docs": "^10.6.0",
41
53
  "@storybook/html-vite": "^10.6.0",
42
54
  "@xmldom/xmldom": "^0.9.12",
43
55
  "polygon-clipping": "^0.15.7",
44
- "prettier": "^3.9.9",
45
56
  "storybook": "^10.6.0",
46
57
  "typescript": "^7.0.2",
47
58
  "vite": "^8.3.1"
48
59
  },
49
- "homepage": "https://github.com/kahwee/sf-map-svg#readme",
60
+ "homepage": "https://kahwee.github.io/sf-map-svg/",
50
61
  "bugs": {
51
62
  "url": "https://github.com/kahwee/sf-map-svg/issues"
52
63
  },
@@ -62,16 +73,20 @@
62
73
  "registry": "https://registry.npmjs.org/"
63
74
  },
64
75
  "scripts": {
65
- "test": "node --test",
66
- "demo": "node examples/build.mjs",
67
- "format": "prettier --write .",
68
- "format:check": "prettier --check .",
69
- "check": "pnpm format:check && pnpm data:check && pnpm typecheck && pnpm test",
70
- "storybook": "storybook dev -p 6006 --host 127.0.0.1 --no-open",
71
- "build-storybook": "storybook build",
76
+ "test": "pnpm build && node --test",
77
+ "demo": "pnpm build && node examples/build.mjs",
78
+ "format": "biome check --write .",
79
+ "format:check": "biome format .",
80
+ "check": "pnpm lint && pnpm data:check && pnpm typecheck && pnpm test",
81
+ "storybook": "pnpm build && storybook dev -p 6006 --host 127.0.0.1 --no-open",
82
+ "build-storybook": "pnpm build && storybook build",
72
83
  "data:catalog": "node scripts/build-data-catalog.mjs",
73
84
  "data:check": "node scripts/build-data-catalog.mjs --check",
74
- "typecheck": "tsc",
75
- "data:normalize-realtor": "node scripts/normalize-realtor.mjs"
85
+ "typecheck": "pnpm build && tsc -p tsconfig.json",
86
+ "data:normalize-realtor": "node scripts/normalize-realtor.mjs",
87
+ "test:package": "node scripts/smoke-package.mjs",
88
+ "build": "node scripts/build.mjs",
89
+ "lint": "biome check .",
90
+ "build:pages": "pnpm demo && node scripts/build-pages.mjs"
76
91
  }
77
92
  }
@@ -1,134 +0,0 @@
1
- {
2
- "type": "FeatureCollection",
3
- "schemaVersion": 1,
4
- "id": "bart-stations",
5
- "title": "San Francisco BART stations",
6
- "coordinateSystem": "WGS84 longitude, latitude (EPSG:4326)",
7
- "definition": {
8
- "kind": "station-centroids",
9
- "description": "Eight station centroids within San Francisco. Entrances, tracks, and other cities are excluded."
10
- },
11
- "sources": [
12
- {
13
- "id": "bart-stations",
14
- "title": "BART official geospatial data",
15
- "url": "https://www.bart.gov/sites/default/files/2025-12/BART-Stations-tracks-entrances-121025.kmz_.zip",
16
- "retrievedAt": "2026-09-25",
17
- "licenseUrl": "https://www.bart.gov/schedules/developers/geo"
18
- }
19
- ],
20
- "features": [
21
- {
22
- "type": "Feature",
23
- "id": "embarcadero",
24
- "bbox": [
25
- -122.3969009943399,
26
- 37.79285391372556,
27
- -122.3969009943399,
28
- 37.79285391372556
29
- ],
30
- "properties": {
31
- "name": "Embarcadero"
32
- },
33
- "geometry": {"type":"Point","coordinates":[-122.3969009943399,37.79285391372556]}
34
- },
35
- {
36
- "type": "Feature",
37
- "id": "montgomery-st",
38
- "bbox": [
39
- -122.4011747105981,
40
- 37.78944952039284,
41
- -122.4011747105981,
42
- 37.78944952039284
43
- ],
44
- "properties": {
45
- "name": "Montgomery St"
46
- },
47
- "geometry": {"type":"Point","coordinates":[-122.4011747105981,37.78944952039284]}
48
- },
49
- {
50
- "type": "Feature",
51
- "id": "powell-st",
52
- "bbox": [
53
- -122.4070176588271,
54
- 37.78487089662774,
55
- -122.4070176588271,
56
- 37.78487089662774
57
- ],
58
- "properties": {
59
- "name": "Powell St"
60
- },
61
- "geometry": {"type":"Point","coordinates":[-122.4070176588271,37.78487089662774]}
62
- },
63
- {
64
- "type": "Feature",
65
- "id": "civic-center-un-plaza",
66
- "bbox": [
67
- -122.4139364152156,
68
- 37.77939386076284,
69
- -122.4139364152156,
70
- 37.77939386076284
71
- ],
72
- "properties": {
73
- "name": "Civic Center/UN Plaza"
74
- },
75
- "geometry": {"type":"Point","coordinates":[-122.4139364152156,37.77939386076284]}
76
- },
77
- {
78
- "type": "Feature",
79
- "id": "16th-st-mission",
80
- "bbox": [
81
- -122.4197081435103,
82
- 37.76505423932072,
83
- -122.4197081435103,
84
- 37.76505423932072
85
- ],
86
- "properties": {
87
- "name": "16th St/Mission"
88
- },
89
- "geometry": {"type":"Point","coordinates":[-122.4197081435103,37.76505423932072]}
90
- },
91
- {
92
- "type": "Feature",
93
- "id": "24th-st-mission",
94
- "bbox": [
95
- -122.4184678760468,
96
- 37.75223145165559,
97
- -122.4184678760468,
98
- 37.75223145165559
99
- ],
100
- "properties": {
101
- "name": "24th St/Mission"
102
- },
103
- "geometry": {"type":"Point","coordinates":[-122.4184678760468,37.75223145165559]}
104
- },
105
- {
106
- "type": "Feature",
107
- "id": "glen-park",
108
- "bbox": [
109
- -122.4337900883829,
110
- 37.73311239451952,
111
- -122.4337900883829,
112
- 37.73311239451952
113
- ],
114
- "properties": {
115
- "name": "Glen Park"
116
- },
117
- "geometry": {"type":"Point","coordinates":[-122.4337900883829,37.73311239451952]}
118
- },
119
- {
120
- "type": "Feature",
121
- "id": "balboa-park",
122
- "bbox": [
123
- -122.4475873574919,
124
- 37.72135058802738,
125
- -122.4475873574919,
126
- 37.72135058802738
127
- ],
128
- "properties": {
129
- "name": "Balboa Park"
130
- },
131
- "geometry": {"type":"Point","coordinates":[-122.4475873574919,37.72135058802738]}
132
- }
133
- ]
134
- }