@kahwee/sf-map-svg 1.2.0 → 1.3.1
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.
- package/CHANGELOG.md +12 -5
- package/README.md +65 -4
- package/SOURCES.md +5 -1
- package/dist/data/README.md +7 -1
- package/dist/data/catalog.json +3 -3
- package/dist/data/guide/bart-stations.json +1 -0
- package/dist/data/guide/coast.json +1 -0
- package/dist/data/guide/highways-detailed.json +1 -0
- package/dist/data/guide/highways.json +1 -0
- package/dist/data/guide/key-roads.json +1 -0
- package/dist/data/guide/landmarks.json +1 -0
- package/dist/data/guide/neighborhoods-realtor.json +1 -0
- package/dist/data/key-roads.json +3837 -6741
- package/dist/data/types.d.ts +1 -0
- package/dist/src/explorer-core.d.ts +5 -0
- package/dist/src/explorer-core.js +1085 -0
- package/dist/src/explorer-data.d.ts +9 -0
- package/dist/src/explorer-data.js +1 -0
- package/dist/src/explorer.d.ts +2 -2
- package/dist/src/explorer.js +28 -1030
- package/dist/src/guide.d.ts +10 -0
- package/dist/src/guide.js +107 -0
- package/dist/src/interactive-data.d.ts +6 -0
- package/dist/src/interactive-data.js +5 -0
- package/dist/src/layers.js +6 -1
- package/dist/src/map-core.d.ts +1 -0
- package/dist/src/map-core.js +9 -2
- package/dist/src/overlays.d.ts +1 -0
- package/dist/src/types.d.ts +2 -0
- package/package.json +11 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InteractiveSFMapData } from './explorer-data.js';
|
|
2
|
+
import type { InteractiveSFMapElement, InteractiveSFMapOptions } from './interactive-data.js';
|
|
3
|
+
/** Compact, source-aware San Francisco guide data. Imports no district or alternate-neighborhood files. */
|
|
4
|
+
export declare const guideMapData: InteractiveSFMapData;
|
|
5
|
+
/** Guide map preset: SFAR neighborhoods, major parks, BART, and curated roads. */
|
|
6
|
+
export declare function createGuideMap(options?: InteractiveSFMapOptions): InteractiveSFMapElement;
|
|
7
|
+
/** Load the detailed versions of the guide's selected geography on explicit request. */
|
|
8
|
+
export declare function loadGuideDetailedData(): Promise<InteractiveSFMapData>;
|
|
9
|
+
export type { InteractiveSFMapData } from './explorer-data.js';
|
|
10
|
+
export type { InteractiveSFMapElement, InteractiveSFMapOptions };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import bart from '../data/guide/bart-stations.json' with { type: 'json' };
|
|
2
|
+
import coast from '../data/guide/coast.json' with { type: 'json' };
|
|
3
|
+
import highways from '../data/guide/highways.json' with { type: 'json' };
|
|
4
|
+
import roads from '../data/guide/key-roads.json' with { type: 'json' };
|
|
5
|
+
import landmarks from '../data/guide/landmarks.json' with { type: 'json' };
|
|
6
|
+
import neighborhoods from '../data/guide/neighborhoods-realtor.json' with { type: 'json' };
|
|
7
|
+
import { createInteractiveSFMapWithData } from './interactive-data.js';
|
|
8
|
+
const neighborhoodData = neighborhoods;
|
|
9
|
+
const highwayData = highways;
|
|
10
|
+
const parkData = landmarks;
|
|
11
|
+
const roadData = roads;
|
|
12
|
+
const stationData = bart;
|
|
13
|
+
/** Compact, source-aware San Francisco guide data. Imports no district or alternate-neighborhood files. */
|
|
14
|
+
export const guideMapData = {
|
|
15
|
+
map: {
|
|
16
|
+
coast: coast.features[0].geometry,
|
|
17
|
+
neighborhoods: neighborhoodData.features.map((feature) => ({
|
|
18
|
+
name: feature.properties.canonicalName,
|
|
19
|
+
geometry: feature.geometry,
|
|
20
|
+
})),
|
|
21
|
+
highways: highwayData.features.map((feature) => ({
|
|
22
|
+
route: String(feature.properties.route),
|
|
23
|
+
geometry: feature.geometry,
|
|
24
|
+
})),
|
|
25
|
+
landmarks: parkData.features.map(({ id, properties, geometry }) => ({
|
|
26
|
+
id,
|
|
27
|
+
...properties,
|
|
28
|
+
geometry,
|
|
29
|
+
})),
|
|
30
|
+
keyRoads: roadData.features.map(({ id, properties, geometry }) => ({
|
|
31
|
+
id,
|
|
32
|
+
...properties,
|
|
33
|
+
sourceNames: properties.sourceNames ?? [],
|
|
34
|
+
segmentIds: properties.segmentIds ?? [],
|
|
35
|
+
geometry,
|
|
36
|
+
})),
|
|
37
|
+
bartStations: stationData.features.flatMap(({ id, properties, geometry }) => geometry.type === 'Point'
|
|
38
|
+
? [{ id, name: properties.name, coordinates: geometry.coordinates }]
|
|
39
|
+
: []),
|
|
40
|
+
},
|
|
41
|
+
neighborhoods: { realtor: neighborhoodData },
|
|
42
|
+
};
|
|
43
|
+
/** Guide map preset: SFAR neighborhoods, major parks, BART, and curated roads. */
|
|
44
|
+
export function createGuideMap(options = {}) {
|
|
45
|
+
return createInteractiveSFMapWithData(guideMapData, {
|
|
46
|
+
mode: 'neighborhoods',
|
|
47
|
+
...options,
|
|
48
|
+
layers: {
|
|
49
|
+
districtFills: false,
|
|
50
|
+
districtLines: false,
|
|
51
|
+
districtLabels: false,
|
|
52
|
+
neighborhoodLines: true,
|
|
53
|
+
neighborhoodLabels: true,
|
|
54
|
+
landmarks: true,
|
|
55
|
+
bartStations: true,
|
|
56
|
+
highways: true,
|
|
57
|
+
keyRoads: true,
|
|
58
|
+
roadLabels: true,
|
|
59
|
+
...options.layers,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Load the detailed versions of the guide's selected geography on explicit request. */
|
|
64
|
+
export async function loadGuideDetailedData() {
|
|
65
|
+
const [coastData, neighborhoodData, parkData, highwayData, roadData, bartData] = await Promise.all([
|
|
66
|
+
import('../data/coast.json', { with: { type: 'json' } }),
|
|
67
|
+
import('../data/neighborhoods-realtor.json', { with: { type: 'json' } }),
|
|
68
|
+
import('../data/landmarks.json', { with: { type: 'json' } }),
|
|
69
|
+
import('../data/guide/highways-detailed.json', { with: { type: 'json' } }),
|
|
70
|
+
import('../data/key-roads.json', { with: { type: 'json' } }),
|
|
71
|
+
import('../data/bart-stations.json', { with: { type: 'json' } }),
|
|
72
|
+
]);
|
|
73
|
+
const detailedNeighborhoods = neighborhoodData.default;
|
|
74
|
+
const detailedRoads = roadData.default;
|
|
75
|
+
const detailedHighways = highwayData.default;
|
|
76
|
+
const detailedParks = parkData.default;
|
|
77
|
+
const detailedStations = bartData.default;
|
|
78
|
+
return {
|
|
79
|
+
map: {
|
|
80
|
+
coast: coastData.default.features[0].geometry,
|
|
81
|
+
neighborhoods: detailedNeighborhoods.features.map((feature) => ({
|
|
82
|
+
name: feature.properties.canonicalName,
|
|
83
|
+
geometry: feature.geometry,
|
|
84
|
+
})),
|
|
85
|
+
highways: detailedHighways.features
|
|
86
|
+
.filter((feature) => ['1', '101', '280'].includes(String(feature.properties.route)))
|
|
87
|
+
.map((feature) => ({
|
|
88
|
+
route: String(feature.properties.route),
|
|
89
|
+
geometry: feature.geometry,
|
|
90
|
+
})),
|
|
91
|
+
landmarks: detailedParks.features.map(({ id, properties, geometry }) => ({
|
|
92
|
+
id,
|
|
93
|
+
...properties,
|
|
94
|
+
geometry,
|
|
95
|
+
})),
|
|
96
|
+
keyRoads: detailedRoads.features.map(({ id, properties, geometry }) => ({
|
|
97
|
+
id,
|
|
98
|
+
...properties,
|
|
99
|
+
geometry,
|
|
100
|
+
})),
|
|
101
|
+
bartStations: detailedStations.features.flatMap(({ id, properties, geometry }) => geometry.type === 'Point'
|
|
102
|
+
? [{ id, name: properties.name, coordinates: geometry.coordinates }]
|
|
103
|
+
: []),
|
|
104
|
+
},
|
|
105
|
+
neighborhoods: { realtor: detailedNeighborhoods },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { InteractiveSFMapData } from './explorer-data.js';
|
|
2
|
+
import type { NeighborhoodExplorerOptions } from './types.js';
|
|
3
|
+
export type { InteractiveSFMapData } from './explorer-data.js';
|
|
4
|
+
export type { InteractiveLayers, MapPadding, MapViewport, NeighborhoodExplorerElement as InteractiveSFMapElement, NeighborhoodExplorerOptions as InteractiveSFMapOptions, NeighborhoodSelection, } from './types.js';
|
|
5
|
+
/** Browser map shell that bundles only the geography supplied by the caller. */
|
|
6
|
+
export declare function createInteractiveSFMapWithData(data: InteractiveSFMapData, options?: NeighborhoodExplorerOptions): import("./types.js").NeighborhoodExplorerElement;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createNeighborhoodExplorerCore } from './explorer-core.js';
|
|
2
|
+
/** Browser map shell that bundles only the geography supplied by the caller. */
|
|
3
|
+
export function createInteractiveSFMapWithData(data, options = {}) {
|
|
4
|
+
return createNeighborhoodExplorerCore({ mode: 'basemap', ...options, interface: 'map' }, data);
|
|
5
|
+
}
|
package/dist/src/layers.js
CHANGED
|
@@ -52,7 +52,12 @@ export function markers(items, { project, colors }) {
|
|
|
52
52
|
.join('')}</g>`;
|
|
53
53
|
}
|
|
54
54
|
export function keyRoads(items, { idPrefix, path, colors }) {
|
|
55
|
-
return `<g data-layer="key-roads" clip-path="url(#${idPrefix}-coast)">${items
|
|
55
|
+
return `<g data-layer="key-roads" clip-path="url(#${idPrefix}-coast)">${items
|
|
56
|
+
.map((road) => {
|
|
57
|
+
const level = road.level === 'secondary' ? 'secondary' : 'primary';
|
|
58
|
+
return `<path data-key-road="${escapeXml(road.id)}" data-key-road-level="${level}" d="${path(road.geometry)}" ${stroke(colors.road, 1)}><title>${escapeXml(road.name)}</title></path>`;
|
|
59
|
+
})
|
|
60
|
+
.join('')}</g>`;
|
|
56
61
|
}
|
|
57
62
|
export function keyRoadLabels(items, { project }) {
|
|
58
63
|
return `<g data-layer="key-road-labels" ${overlayLabel} fill="#77736b">${items
|
package/dist/src/map-core.d.ts
CHANGED
package/dist/src/map-core.js
CHANGED
|
@@ -46,6 +46,7 @@ let sequence = 0;
|
|
|
46
46
|
/** Make an offline SVG and the matching longitude/latitude projection. */
|
|
47
47
|
export function createSFMapWithData(options, data) {
|
|
48
48
|
const { theme = 'districts', width = 800, height = 800, padding = 28, year = 2022, districtLines = true, neighborhoodLines = false, districtFills = true, districtLabels = true, labels = true, highways = false, keyRoads = false, landmarks = false, bartStations = false, markers = [], title = 'San Francisco map', idPrefix = `sf-map-${++sequence}`, } = options;
|
|
49
|
+
const roadLabels = options.roadLabels ?? keyRoads;
|
|
49
50
|
if (![width, height, padding].every(Number.isFinite) ||
|
|
50
51
|
width <= 0 ||
|
|
51
52
|
height <= 0 ||
|
|
@@ -87,7 +88,7 @@ export function createSFMapWithData(options, data) {
|
|
|
87
88
|
}))
|
|
88
89
|
: [];
|
|
89
90
|
const parts = [
|
|
90
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" role="img" aria-labelledby="${idPrefix}-title" data-sf-map="" data-year="${year}" style="max-width:100%;height:auto"><title id="${idPrefix}-title">${escapeXml(title)}</title><desc>San Francisco supervisorial district boundaries (${year}).${neighborhoodLines ? ' Dashed lines show SFAR realtor neighborhood areas, defined in August 2010.' : ''}${landmarks ? ' Highlighted areas show six parks and landmarks.' : ''}${keyRoads ? ' Thin gray lines show
|
|
91
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" role="img" aria-labelledby="${idPrefix}-title" data-sf-map="" data-year="${year}" style="max-width:100%;height:auto"><title id="${idPrefix}-title">${escapeXml(title)}</title><desc>San Francisco supervisorial district boundaries (${year}).${neighborhoodLines ? ' Dashed lines show SFAR realtor neighborhood areas, defined in August 2010.' : ''}${landmarks ? ' Highlighted areas show six parks and landmarks.' : ''}${keyRoads ? ' Thin gray lines show selected road corridors.' : ''}${bartStations ? ' Rings mark the eight San Francisco BART stations.' : ''} Geometry from DataSF${bartStations ? ' and BART' : ''}. See package SOURCES.md.</desc><defs><clipPath id="${idPrefix}-coast"><path d="${coastPath}" fill-rule="evenodd" clip-rule="evenodd"/></clipPath></defs><rect width="${width}" height="${height}" fill="${escapeXml(colors.water)}"/><g data-layer="geography"><path data-layer="coast" d="${coastPath}" fill="${escapeXml(colors.land)}" fill-rule="evenodd"/>`,
|
|
91
92
|
];
|
|
92
93
|
// Explicit drawing order keeps optional overlays and user markers predictable.
|
|
93
94
|
if (districtFills)
|
|
@@ -107,13 +108,19 @@ export function createSFMapWithData(options, data) {
|
|
|
107
108
|
parts.push(layers.districtLabels(districts, context));
|
|
108
109
|
if (labels && landmarks)
|
|
109
110
|
parts.push(layers.landmarkLabels(landmarkData, context));
|
|
110
|
-
if (labels &&
|
|
111
|
+
if (labels && roadLabels)
|
|
111
112
|
parts.push(layers.keyRoadLabels(roadData, context));
|
|
112
113
|
if (bartStations)
|
|
113
114
|
parts.push(layers.bartStations(stationData, context));
|
|
114
115
|
for (const overlay of options.overlays ?? []) {
|
|
115
116
|
if (!overlay.id || !/^[A-Za-z0-9_-]+$/.test(overlay.id))
|
|
116
117
|
throw new TypeError('Overlay IDs must contain only letters, numbers, underscores, or hyphens.');
|
|
118
|
+
if (overlay.strokeWidth !== undefined &&
|
|
119
|
+
(!Number.isFinite(overlay.strokeWidth) || overlay.strokeWidth < 0))
|
|
120
|
+
throw new RangeError('Overlay strokeWidth must be a finite nonnegative number.');
|
|
121
|
+
if (overlay.fillOpacity !== undefined &&
|
|
122
|
+
(!Number.isFinite(overlay.fillOpacity) || overlay.fillOpacity < 0 || overlay.fillOpacity > 1))
|
|
123
|
+
throw new RangeError('Overlay fillOpacity must be a finite number from 0 to 1.');
|
|
117
124
|
parts.push(`<path data-overlay-id="${escapeXml(overlay.id)}"${overlay.label ? ` aria-label="${escapeXml(overlay.label)}"` : ''} d="${path(overlay.geometry)}" fill="${escapeXml(overlay.fill ?? 'none')}" fill-opacity="${overlay.fillOpacity ?? 1}" stroke="${escapeXml(overlay.stroke ?? colors.road)}" stroke-width="${overlay.strokeWidth ?? 2}" vector-effect="non-scaling-stroke"${overlay.visible === false ? ' display="none"' : ''}/>`);
|
|
118
125
|
}
|
|
119
126
|
parts.push(layers.markers(markers, context), '</g></svg>');
|
package/dist/src/overlays.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type Landmark = (typeof landmarks)[number];
|
|
|
16
16
|
export type BartStation = (typeof bartStations)[number];
|
|
17
17
|
export declare const keyRoads: {
|
|
18
18
|
name: string;
|
|
19
|
+
level?: 'primary' | 'secondary';
|
|
19
20
|
sourceNames: readonly string[];
|
|
20
21
|
label: Position;
|
|
21
22
|
segmentIds: readonly string[];
|
package/dist/src/types.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface SFMapOptions {
|
|
|
34
34
|
labels?: boolean;
|
|
35
35
|
highways?: boolean;
|
|
36
36
|
keyRoads?: boolean;
|
|
37
|
+
roadLabels?: boolean;
|
|
37
38
|
landmarks?: boolean;
|
|
38
39
|
bartStations?: boolean;
|
|
39
40
|
markers?: MapMarker[];
|
|
@@ -61,6 +62,7 @@ export interface InteractiveLayers {
|
|
|
61
62
|
bartStations?: boolean;
|
|
62
63
|
highways?: boolean;
|
|
63
64
|
keyRoads?: boolean;
|
|
65
|
+
roadLabels?: boolean;
|
|
64
66
|
}
|
|
65
67
|
export interface NeighborhoodSelection {
|
|
66
68
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahwee/sf-map-svg",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Offline SVG maps of San Francisco with district boundaries, parks, landmarks, and BART stations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -31,6 +31,14 @@
|
|
|
31
31
|
"types": "./dist/src/interactive.d.ts",
|
|
32
32
|
"import": "./dist/src/interactive.js"
|
|
33
33
|
},
|
|
34
|
+
"./interactive-data": {
|
|
35
|
+
"types": "./dist/src/interactive-data.d.ts",
|
|
36
|
+
"import": "./dist/src/interactive-data.js"
|
|
37
|
+
},
|
|
38
|
+
"./guide": {
|
|
39
|
+
"types": "./dist/src/guide.d.ts",
|
|
40
|
+
"import": "./dist/src/guide.js"
|
|
41
|
+
},
|
|
34
42
|
"./transit": {
|
|
35
43
|
"types": "./dist/src/transit.d.ts",
|
|
36
44
|
"import": "./dist/src/transit.js"
|
|
@@ -88,6 +96,8 @@
|
|
|
88
96
|
"data:check": "node scripts/build-data-catalog.mjs --check",
|
|
89
97
|
"typecheck": "pnpm build && tsc -p tsconfig.json",
|
|
90
98
|
"data:normalize-realtor": "node scripts/normalize-realtor.mjs",
|
|
99
|
+
"data:guide": "node scripts/build-guide-data.mjs",
|
|
100
|
+
"report:guide": "node scripts/report-guide-bundle.mjs",
|
|
91
101
|
"test:package": "node scripts/smoke-package.mjs",
|
|
92
102
|
"build": "node scripts/build.mjs",
|
|
93
103
|
"lint": "biome check .",
|