@kahwee/sf-map-svg 0.3.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.
- package/CHANGELOG.md +44 -0
- package/LICENSE +5 -0
- package/README.md +133 -0
- package/SOURCES.md +77 -0
- package/data/README.md +100 -0
- package/data/bart-stations.json +134 -0
- package/data/catalog.json +4699 -0
- package/data/coast.json +36 -0
- package/data/districts-2002.json +325 -0
- package/data/districts-2012.json +325 -0
- package/data/districts-2022.json +325 -0
- package/data/highways.json +4138 -0
- package/data/index.d.ts +117 -0
- package/data/index.js +69 -0
- package/data/landmarks.json +167 -0
- package/data/neighborhoods-analysis.json +817 -0
- package/data/neighborhoods-realtor.json +1978 -0
- package/data/neighborhoods.json +2267 -0
- package/package.json +77 -0
- package/src/data.js +35 -0
- package/src/geometry.d.ts +8 -0
- package/src/geometry.js +51 -0
- package/src/immutable.js +8 -0
- package/src/index.d.ts +51 -0
- package/src/index.js +122 -0
- package/src/layers.js +54 -0
- package/src/overlays.js +18 -0
- package/src/svg.js +12 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- Enable public npm distribution with explicit public registry access.
|
|
6
|
+
|
|
7
|
+
- Remove realtor boundary sliver overlaps while preserving all 92 neighborhoods and their combined footprint; enforce disjoint interiors in regression tests.
|
|
8
|
+
|
|
9
|
+
- Use the 92 SFAR realtor neighborhoods by default for map outlines, data lookup, and Storybook. Other source collections remain available.
|
|
10
|
+
|
|
11
|
+
- Move all geographic data to exported canonical GeoJSON files, preserving district display extras.
|
|
12
|
+
- Expose complete SF Find (117), analysis (41), and realtor (92) neighborhood collections with source-specific canonical names and documented aliases.
|
|
13
|
+
- Add immutable data helpers, exact-name lookup, source-aware search, and a typed geometry export.
|
|
14
|
+
- Split SVG layers from orchestration and reuse projected district paths.
|
|
15
|
+
- Reject longitude values that could overflow SVG coordinates.
|
|
16
|
+
- Add neighborhood explorer stories, JSON downloads, independent overlay stories, and data integrity checks.
|
|
17
|
+
|
|
18
|
+
## 0.2.0
|
|
19
|
+
|
|
20
|
+
- Add optional park and landmark highlights using DataSF property boundaries.
|
|
21
|
+
- Add all eight San Francisco BART stations using official station coordinates.
|
|
22
|
+
- Add overlay color options, TypeScript declarations, source records, and SVG checks.
|
|
23
|
+
- Add Storybook 10.6 with eight interactive examples and API controls.
|
|
24
|
+
- Add usage examples, contributor instructions, and AGENTS.md.
|
|
25
|
+
- Update GitHub Actions and validate Storybook and package builds on Node 22 and 26.
|
|
26
|
+
- Configure weekly Dependabot updates for development dependencies and Actions.
|
|
27
|
+
- Keep existing layer defaults unchanged, runtime dependencies at zero, and distribution private.
|
|
28
|
+
|
|
29
|
+
## 0.1.1
|
|
30
|
+
|
|
31
|
+
- Separate geometry and projection helpers from SVG layer rendering.
|
|
32
|
+
- Cache immutable coast bounds across renders.
|
|
33
|
+
- Validate complete SVG documents as XML in regression tests.
|
|
34
|
+
- Remove invalid XML control characters from user-supplied labels.
|
|
35
|
+
- Specify even-odd clipping for coastline holes.
|
|
36
|
+
- Standardize formatting and document contribution and private release workflows.
|
|
37
|
+
- Distribute through private GitHub releases; disable npm publication.
|
|
38
|
+
- Exclude original Site application code and internal source identifiers.
|
|
39
|
+
|
|
40
|
+
## 0.1.0
|
|
41
|
+
|
|
42
|
+
- Extract Site version 6 into a standalone SVG renderer.
|
|
43
|
+
- Bundle 2002, 2012 and 2022 districts plus optional SF Find neighborhood boundaries.
|
|
44
|
+
- Preserve source provenance, palette, coast, labels and optional highways.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright (c) 2026 KahWee Teng. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Public availability does not grant a license to redistribute or modify the software.
|
|
4
|
+
The geographic data comes from DataSF and remains subject to its source terms;
|
|
5
|
+
see SOURCES.md. Those third-party terms are not replaced by this notice.
|
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# San Francisco SVG maps
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Private package extracted from KahWee’s **San Francisco District Map** Site. Draws a self-contained SVG with bundled geometry and no runtime dependencies, tiles, WebGL, or network requests.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { renderSFMap, createSFMap } from '@kahwee/sf-map-svg';
|
|
9
|
+
|
|
10
|
+
const svg = renderSFMap({
|
|
11
|
+
year: 2022,
|
|
12
|
+
districtLines: true,
|
|
13
|
+
landmarks: true, // parks with labels
|
|
14
|
+
bartStations: true, // all eight SF stations
|
|
15
|
+
highways: true,
|
|
16
|
+
neighborhoodLines: true, // optional; off by default
|
|
17
|
+
markers: [{ id: 'dolores', lng: -122.4269, lat: 37.7596, label: 'Dolores Park' }],
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Write `svg` to a `.svg` file or embed it in your page. For Astro, render it with `<div set:html={svg} />`. Text and attribute values are XML escaped.
|
|
22
|
+
|
|
23
|
+
## Layers and options
|
|
24
|
+
|
|
25
|
+
| Option | Default | Purpose |
|
|
26
|
+
| ------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `year` | `2022` | District boundaries: `2002`, `2012`, or `2022` |
|
|
28
|
+
| `districtLines` | `true` | Supervisorial district outlines |
|
|
29
|
+
| `neighborhoodLines` | `false` | Dashed SFAR realtor neighborhood outlines |
|
|
30
|
+
| `districtFills` | `true` | Original Site’s eleven muted district colors |
|
|
31
|
+
| `districtLabels` | `true` | District number badges |
|
|
32
|
+
| `highways` | `false` | Original Site’s highway geometry |
|
|
33
|
+
| `landmarks` | `false` | Golden Gate Park, Presidio, Lincoln Park, Twin Peaks, Dolores Park, and McLaren Park |
|
|
34
|
+
| `bartStations` | `false` | Eight San Francisco BART stations with blue rings and names |
|
|
35
|
+
| `width`, `height` | `800`, `800` | SVG viewBox and intrinsic size |
|
|
36
|
+
| `padding` | `28` | Space around the coast |
|
|
37
|
+
| `markers` | `[]` | Points with `id`, `lng`, `lat`, optional `label`, `color`, `selected` |
|
|
38
|
+
| `colors` | Built-in palette | Override `water`, `land`, `district`, `neighborhood`, `highway`, `park`, `landmark`, `bart`, `label`, `marker`, `selected` |
|
|
39
|
+
| `title` | `San Francisco map` | Accessible SVG title |
|
|
40
|
+
| `idPrefix` | Unique per process | Set explicitly for deterministic output or independent server renders |
|
|
41
|
+
|
|
42
|
+
For a plain outline map, set `districtFills: false`. Neighborhood areas are **August 2010 SFAR realtor areas**, not legal boundaries or a historical layer matched to the district year. See [SOURCES.md](SOURCES.md).
|
|
43
|
+
|
|
44
|
+
`createSFMap(options)` returns `{ svg, project, viewBox }`. `project([longitude, latitude])` gives matching SVG coordinates for custom overlays. Named exports also include `districtYears`, `districtColors`, and `neighborhoodNames`.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
pnpm add @kahwee/sf-map-svg
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The package is published publicly on npm. Geographic JSON files are included in the package. See `LICENSE` and `SOURCES.md` for software and source-data rights.
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
pnpm install --frozen-lockfile
|
|
58
|
+
pnpm test
|
|
59
|
+
pnpm demo
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Open `examples/generated/index.html` to compare district and neighborhood maps. Generated SVG files are there too. See [CONTRIBUTING.md](CONTRIBUTING.md) for source structure, checks and release steps. GitHub Actions runs validation; npm releases use public access.
|
|
63
|
+
|
|
64
|
+
The package uses a small Mercator SVG renderer while retaining the Site’s boundary geometry, coastline, palette, district labels, and highway data.
|
|
65
|
+
|
|
66
|
+
Enable `landmarks`, `bartStations`, and `highways` together for the featured example. Park fills use `colors.park`, park labels use `colors.landmark`, and station rings and labels use `colors.bart`. These current geographic overlays are independent of the district year; BART stations are city-only (Daly City is outside the map). Station positions are geographic points, not a route diagram.
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
### Landmarks, BART stations, and highways
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { renderSFMap } from '@kahwee/sf-map-svg';
|
|
74
|
+
import { writeFile } from 'node:fs/promises';
|
|
75
|
+
|
|
76
|
+
await writeFile(
|
|
77
|
+
'san-francisco.svg',
|
|
78
|
+
renderSFMap({
|
|
79
|
+
landmarks: true,
|
|
80
|
+
bartStations: true,
|
|
81
|
+
highways: true,
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Plain map with a selected place
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
const svg = renderSFMap({
|
|
90
|
+
districtFills: false,
|
|
91
|
+
districtLabels: false,
|
|
92
|
+
landmarks: true,
|
|
93
|
+
markers: [{ id: 'dolores', lng: -122.4269, lat: 37.7596, label: 'Dolores Park', selected: true }],
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Match a site's colors
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
const svg = renderSFMap({
|
|
101
|
+
landmarks: true,
|
|
102
|
+
bartStations: true,
|
|
103
|
+
colors: { park: '#c4d4b1', landmark: '#3e6346', bart: '#795285' },
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Storybook
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
pnpm storybook # http://127.0.0.1:6006
|
|
111
|
+
pnpm build-storybook # static output in storybook-static/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Map stories cover the default map, combined and independent landmark/BART layers, neighborhoods, outlines, historical district years, custom markers, a custom palette, and a narrow map. The Data / Neighborhood explorer adds examples for comparing Mission, Outer Mission, SoMa, and NoPa across source definitions. Controls edit map options live; the Docs tab shows usage examples. Storybook and Vite are development dependencies only and are excluded from the package archive. Development requires Node 22.12+ and pnpm 12. Only esbuild's dependency build script is enabled in `pnpm-workspace.yaml`.
|
|
115
|
+
|
|
116
|
+
Dependabot checks npm dependencies and GitHub Actions weekly, grouping Storybook updates. CI validates formatting, SVG tests, generated examples, Storybook builds, and package creation on Node 22 and 26. Dependency PRs require review; updates are not merged automatically.
|
|
117
|
+
|
|
118
|
+
## Accessible JSON data and neighborhood lookup
|
|
119
|
+
|
|
120
|
+
All map geometry is available through stable JSON package exports. There are three district files (2002, 2012, 2022), the full 117 SF Find neighborhoods, 41 analysis neighborhoods, 92 realtor-defined areas, and separate coastline, highway, landmark, and BART files. Neighborhood records include a canonical display name, exact source name, stable ID, aliases where documented, source definition, and full polygon geometry.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import neighborhoods from '@kahwee/sf-map-svg/data/neighborhoods-realtor.json' with { type: 'json' };
|
|
124
|
+
import districts2022 from '@kahwee/sf-map-svg/data/districts-2022.json' with { type: 'json' };
|
|
125
|
+
import { getNeighborhood, searchNeighborhoods } from '@kahwee/sf-map-svg/data';
|
|
126
|
+
|
|
127
|
+
const mission = getNeighborhood('Inner Mission');
|
|
128
|
+
const outerMission = getNeighborhood('Outer Mission');
|
|
129
|
+
const nopa = getNeighborhood('NoPa', { source: 'realtor' });
|
|
130
|
+
const matchingDefinitions = searchNeighborhoods('mission');
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
These are 250 **source-specific definitions**, not 250 distinct neighborhoods. Canonical names are package display names, and boundaries reflect each documented source rather than a claimed universal consensus. Mission and Outer Mission remain distinct. JSON files are the source of truth used by the renderer; the default map and lookup use the 92 SFAR realtor neighborhoods. See [the data API guide](data/README.md) for all filenames, schema, lookup rules, source comparisons, and custom SVG overlays. Storybook provides downloadable JSON files beside its neighborhood examples.
|
package/SOURCES.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Geometry and provenance
|
|
2
|
+
|
|
3
|
+
## Map extraction
|
|
4
|
+
|
|
5
|
+
District geometry, coast, label anchors, highway geometry, and the pastel district palette were extracted from the San Francisco District Map on September 25, 2026. Only map rendering and public geographic data are distributed. Site account identifiers, original application code, and ballot overlays are excluded.
|
|
6
|
+
|
|
7
|
+
## Districts and coastline
|
|
8
|
+
|
|
9
|
+
DataSF datasets used by the original Site:
|
|
10
|
+
|
|
11
|
+
| Layer | Source |
|
|
12
|
+
| ---------------------------- | ---------------------------------------------- |
|
|
13
|
+
| 2002 supervisorial districts | https://data.sf.gov/resource/qdm2-fi8r.geojson |
|
|
14
|
+
| 2012 supervisorial districts | https://data.sf.gov/resource/keex-zmn4.geojson |
|
|
15
|
+
| 2022 supervisorial districts | https://data.sf.gov/resource/f2zs-jevy.geojson |
|
|
16
|
+
| Display land mask | https://data.sf.gov/resource/hcgx-vtsb.geojson |
|
|
17
|
+
| Highways | https://data.sf.gov/resource/3psu-pn9h.geojson |
|
|
18
|
+
|
|
19
|
+
The Site prepared a common coastline using polygon intersections. It excludes the 2002 district-zero water area and tiny survey/reclamation differences between years; internal district boundaries remain unchanged. It represents about 99.631% of the current trimmed land mask. This package preserves that display geometry and includes Treasure Island; it is not a cadastral or navigational map and does not include the Farallon Islands.
|
|
20
|
+
|
|
21
|
+
## Optional neighborhoods
|
|
22
|
+
|
|
23
|
+
117 SF Find Neighborhoods downloaded September 25, 2026:
|
|
24
|
+
|
|
25
|
+
- GeoJSON: https://data.sf.gov/resource/gfpk-269f.geojson?$limit=500
|
|
26
|
+
- Catalog: https://data.sf.gov/Geographic-Locations-and-Boundaries/SF-Find-Neighborhoods/pty2-tcw4
|
|
27
|
+
|
|
28
|
+
The Mayor’s Office of Neighborhood Services defined these areas in **2006** for SF Find. They convey approximate neighborhood locations, not hard demarcations. SF Find remains available as an alternative JSON collection. The default renderer now uses the August 2010 SFAR realtor collection, independently of district year, clipped to the Site’s display coastline.
|
|
29
|
+
|
|
30
|
+
## Rights
|
|
31
|
+
|
|
32
|
+
Source geometry is provided by the City and County of San Francisco through DataSF, subject to the source datasets’ terms: https://datasf.org/opendata/terms-of-use/
|
|
33
|
+
|
|
34
|
+
Package licensing does not change rights in the underlying public data. Retain source attribution when redistributing maps or data.
|
|
35
|
+
|
|
36
|
+
## Parks and landmark areas
|
|
37
|
+
|
|
38
|
+
Downloaded September 25, 2026:
|
|
39
|
+
|
|
40
|
+
- Recreation and Parks Properties: https://data.sf.gov/resource/gtr9-ntp6.geojson?$limit=1000
|
|
41
|
+
- Presidio boundary: https://data.sf.gov/resource/jt6f-vx2z.geojson
|
|
42
|
+
|
|
43
|
+
`data/landmarks.json` retains full source coordinates for six selected landmark areas. Golden Gate Park combines property sections 1–7 into one MultiPolygon; section boundaries are not stroked. Lincoln Park, John McLaren Park, Mission Dolores Park, and Twin Peaks use their named RPD properties. The Presidio uses its separate boundary dataset. Label anchors and offsets are hand-positioned for city-scale legibility. These are property areas, not neighborhood approximations, and do not vary with the district year.
|
|
44
|
+
|
|
45
|
+
## BART stations
|
|
46
|
+
|
|
47
|
+
Downloaded September 25, 2026 from BART's [geospatial data page](https://www.bart.gov/schedules/developers/geo):
|
|
48
|
+
|
|
49
|
+
- Official station-centroid KML archive: https://www.bart.gov/sites/default/files/2025-12/BART-Stations-tracks-entrances-121025.kmz_.zip
|
|
50
|
+
|
|
51
|
+
The `BART Station` folder supplies names and unrounded longitude/latitude for the eight San Francisco stations: Embarcadero, Montgomery St, Powell St, Civic Center/UN Plaza, 16th St/Mission, 24th St/Mission, Glen Park, and Balboa Park. Entrances, tracks, and stations outside city limits are excluded. Station locations are a current overlay, not historical station inventories matched to each district year. BART data retains its source rights independently of the package license.
|
|
52
|
+
|
|
53
|
+
## Public JSON collections and neighborhood naming
|
|
54
|
+
|
|
55
|
+
On September 25, 2026, the previously bundled district, coast, highway, park, and station geometry was moved unchanged to `data/*.json`. District `properties.displayExtras` and label points retain the original map's presentation geometry and island badges. The district JSON files describe processed display maps, not untouched source downloads. Regression digests preserve the original district and SF Find coordinate sequences.
|
|
56
|
+
|
|
57
|
+
The SF Find source was fetched again and its 117 geometries matched the bundled geometry exactly. Two additional complete collections were downloaded on September 25, 2026:
|
|
58
|
+
|
|
59
|
+
- Analysis Neighborhoods (41): https://data.sf.gov/resource/j2bu-swwd.geojson?$limit=100
|
|
60
|
+
- Realtor Neighborhoods (92; August 2010 SFAR definitions): https://data.sf.gov/resource/2kjj-ysvr.geojson?$limit=500
|
|
61
|
+
|
|
62
|
+
The [Analysis Neighborhoods metadata](https://catalog.data.gov/dataset/analysis-neighborhoods) explains the census-tract aggregation and explicitly states that these are not official neighborhood boundaries. The [Realtor Neighborhoods dataset](https://data.sf.gov/d/2kjj-ysvr) identifies SFAR as the source of those alternative definitions. Collection counts mean 250 source-specific definitions, not 250 unique neighborhoods or an exhaustive inventory of every locally used name. Same-name polygons are not merged across sources.
|
|
63
|
+
|
|
64
|
+
Canonical names default to original source labels. The package normalizes Haight-Ashbury and Fisherman's Wharf punctuation where the SF Find source differs, preserving the exact labels in `sourceName`. Curated lookup aliases use these naming references; the references support names, not agreement with the GeoJSON boundary:
|
|
65
|
+
|
|
66
|
+
- Mission / Mission District / The Mission: https://www.sftravel.com/neighborhoods/mission-district and https://www.sftravel.com/neighborhoods
|
|
67
|
+
- South of Market / SoMa: https://www.sftravel.com/neighborhoods/soma-yerba-buena
|
|
68
|
+
- North Panhandle / NoPa / North of the Panhandle: https://www.sftravel.com/article/where-to-eat-drink-san-franciscos-nopa
|
|
69
|
+
- Haight-Ashbury and Fisherman's Wharf display spellings: https://www.sftravel.com/neighborhoods
|
|
70
|
+
|
|
71
|
+
No survey of resident consensus is claimed. Composite areas are not converted to aliases of their component neighborhoods. Mission and Outer Mission remain separate identities. Detailed schema and access examples are in `data/README.md`.
|
|
72
|
+
|
|
73
|
+
## Non-overlapping realtor topology
|
|
74
|
+
|
|
75
|
+
On September 25, 2026, pairwise polygon intersection checks found 64 overlapping pairs in the original 92-area realtor dataset. These were boundary slivers totaling approximately 1.05 square meters (local planar estimate). `pnpm data:normalize-realtor` removes shared interior area by assigning it to the lexicographically first stable neighborhood ID and subtracting it from the other feature. This is a deterministic geometric tie-break for source slivers, not a new claim about legal boundaries.
|
|
76
|
+
|
|
77
|
+
The cleanup retains all 92 identities, names, and source codes. It uses no rounding or buffers, recalculates affected bounding boxes, and verifies that the union of all neighborhood areas is unchanged. Shared edges and vertices remain valid. `data/neighborhoods-realtor.json` records the transformation in `topology`; its polygons are normalized derivatives of the cited source. Regression tests require empty pairwise polygon intersections, non-overlapping component polygons, the original union digest, and an idempotent cleanup. Polygon clipping is a development dependency only; rendering remains dependency-free.
|
package/data/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Geographic data API
|
|
2
|
+
|
|
3
|
+
The JSON files here are the geographic source of truth, not generated copies of renderer JavaScript. Each map file is a GeoJSON `FeatureCollection` with WGS84 `[longitude, latitude]` coordinates, a schema version, source URLs, retrieval dates, and a definition explaining its scope.
|
|
4
|
+
|
|
5
|
+
The renderer, `neighborhoods` convenience export, and `getNeighborhood` default to the **92 SFAR realtor areas**. Import `neighborhoods-realtor.json` directly for that same geometry. The filename `neighborhoods.json` continues to identify the separate SF Find collection.
|
|
6
|
+
|
|
7
|
+
## Available files
|
|
8
|
+
|
|
9
|
+
| File | Contents |
|
|
10
|
+
| ----------------------------- | ------------------------------------------------------------------- |
|
|
11
|
+
| `districts-2002.json` | 11 supervisorial districts for 2002 |
|
|
12
|
+
| `districts-2012.json` | 11 supervisorial districts for 2012 |
|
|
13
|
+
| `districts-2022.json` | 11 supervisorial districts for 2022 |
|
|
14
|
+
| `neighborhoods.json` | All 117 SF Find neighborhoods (2006 definitions) |
|
|
15
|
+
| `neighborhoods-analysis.json` | All 41 city analysis neighborhoods |
|
|
16
|
+
| `neighborhoods-realtor.json` | All 92 SFAR areas in the August 2010 dataset |
|
|
17
|
+
| `coast.json` | The renderer's common display coastline |
|
|
18
|
+
| `highways.json` | The original map's highway geometry |
|
|
19
|
+
| `landmarks.json` | Six selected park/landmark property areas |
|
|
20
|
+
| `bart-stations.json` | Eight San Francisco station points |
|
|
21
|
+
| `catalog.json` | Dataset index and searchable neighborhood metadata without geometry |
|
|
22
|
+
|
|
23
|
+
The three neighborhood collections contain **250 source-specific definitions**, not 250 distinct neighborhoods. They cover the full inventories of these sources; they do not establish an exhaustive list of every informal or recently coined name. Residents, reporting agencies, and real-estate maps use different boundaries. Compare definitions without merging them merely because their names match.
|
|
24
|
+
|
|
25
|
+
## Import a JSON file
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import neighborhoods from '@kahwee/sf-map-svg/data/neighborhoods-realtor.json' with { type: 'json' };
|
|
29
|
+
import districts from '@kahwee/sf-map-svg/data/districts-2022.json' with { type: 'json' };
|
|
30
|
+
|
|
31
|
+
const mission = neighborhoods.features.find((f) => f.id === 'inner-mission');
|
|
32
|
+
console.log(mission.properties.canonicalName, mission.geometry);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Node 22.12+ supports this syntax. Bundlers may also support JSON imports without the import attribute. Files are included in the package archive; non-JavaScript consumers can parse the same JSON files directly. The repository remains private; these are public package entry points, not a publicly hosted API. Consumers who need only one dataset should import its JSON subpath rather than the convenience module, which loads all collections.
|
|
36
|
+
|
|
37
|
+
## Names and definitions
|
|
38
|
+
|
|
39
|
+
Every neighborhood feature contains:
|
|
40
|
+
|
|
41
|
+
- `id`: a stable slug within its source. Use `(definitionSource, id)` as the identity across datasets.
|
|
42
|
+
- `canonicalName` and `name`: the package's display name. This is not a claim of legal or universal authority.
|
|
43
|
+
- `sourceName`: the exact original dataset name, preserved even when display punctuation is normalized.
|
|
44
|
+
- `aliases`: evidence-backed alternative names for lookup; empty when none are recorded. Case and punctuation differences do not require separate aliases.
|
|
45
|
+
- `definitionSource`: `sf-find`, `analysis`, or `realtor`.
|
|
46
|
+
- `nameSources`: references supporting curated aliases or spelling changes.
|
|
47
|
+
- `geometry` and `bbox`: the complete polygon and its geographic extent, not a pin or estimated rectangle.
|
|
48
|
+
|
|
49
|
+
The collection's `definition` describes how its boundaries were chosen. Mission and Outer Mission are distinct records in each source that defines them. The default realtor collection names the area “Inner Mission” and keeps “Mission Dolores” separate. Use those source names; “Mission” alone is not silently substituted. With `{ source: 'sf-find' }`, “Mission District” and “The Mission” resolve to Mission, never Outer Mission. “NoPa” resolves to the realtor source's North Panhandle feature; that does not claim everyone agrees with the realtor polygon. Composite source areas such as “Laurel Heights / Jordan Park” remain composite; neither name alone is silently equated with the combined polygon.
|
|
50
|
+
|
|
51
|
+
## Look up or search
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import {
|
|
55
|
+
getNeighborhood,
|
|
56
|
+
searchNeighborhoods,
|
|
57
|
+
neighborhoodCollections,
|
|
58
|
+
districtMaps,
|
|
59
|
+
} from '@kahwee/sf-map-svg/data';
|
|
60
|
+
|
|
61
|
+
const mission = getNeighborhood('Inner Mission'); // SFAR realtor definitions by default
|
|
62
|
+
const outerMission = getNeighborhood('Outer Mission');
|
|
63
|
+
const analysisMission = getNeighborhood('Mission', { source: 'analysis' });
|
|
64
|
+
const nopa = getNeighborhood('NoPa', { source: 'realtor' });
|
|
65
|
+
const candidates = searchNeighborhoods('mission'); // names + sources, no geometry
|
|
66
|
+
const allSFNames = searchNeighborhoods('', { source: 'sf-find' });
|
|
67
|
+
const allAnalysisPolygons = neighborhoodCollections.analysis;
|
|
68
|
+
const historicalDistricts = districtMaps[2012];
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Lookup is exact after case/punctuation normalization, returns `undefined` for an unknown name, and throws for an unknown source. Search matches substrings and preserves every source-specific result. The convenience module's shared data is deeply frozen; use `structuredClone(feature)` if you need an editable copy. Do not mutate imported datasets used by the renderer.
|
|
72
|
+
|
|
73
|
+
## Draw a neighborhood with the existing projection
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
import { createSFMap } from '@kahwee/sf-map-svg';
|
|
77
|
+
import { getNeighborhood } from '@kahwee/sf-map-svg/data';
|
|
78
|
+
import { geometryPath } from '@kahwee/sf-map-svg/geometry';
|
|
79
|
+
|
|
80
|
+
const map = createSFMap({ districtFills: false });
|
|
81
|
+
const mission = getNeighborhood('Inner Mission');
|
|
82
|
+
const pathData = geometryPath(mission.geometry, map.project);
|
|
83
|
+
// Use pathData as an SVG <path d="..."> over map.svg.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`geometryPath` handles Polygon, MultiPolygon, LineString, MultiLineString, and GeometryCollection; points use `map.project` and a marker element. Storybook's **Data / Neighborhood explorer** demonstrates the complete overlay and offers JSON downloads.
|
|
87
|
+
|
|
88
|
+
## District display metadata
|
|
89
|
+
|
|
90
|
+
District `geometry` is the primary district geometry retained from the original map. `properties.displayExtras` preserves separate island/coast display additions, which can be polygons, lines, or GeometryCollections. To reproduce the existing SVG, draw both; do not coerce every extra into a polygon or use display extras for area calculations. `label` and `labelPoints` provide badge anchors. Each feature’s `bbox` covers its primary `geometry` only; include `displayExtras` when fitting the full displayed district, especially District 6’s island. These are processed display maps, not newly downloaded raw or legal district boundaries. See `SOURCES.md` for the original extraction.
|
|
91
|
+
|
|
92
|
+
## Maintaining data
|
|
93
|
+
|
|
94
|
+
Edit the canonical JSON only. Keep coordinate precision and source labels; record provenance in both dataset metadata and `SOURCES.md`. Add aliases only with supporting references. Run `pnpm data:catalog` after changing names, metadata, inventories, or bounds, then `pnpm check`. CI checks catalog freshness, inventories, coordinate bounds, immutable helper data, lookup behavior, and original geometry digests. Update those digests only for intentional, sourced geography changes.
|
|
95
|
+
|
|
96
|
+
## Non-overlap guarantee for the default realtor collection
|
|
97
|
+
|
|
98
|
+
The 92 realtor neighborhoods have disjoint interiors. Shared borders and corner points are allowed. The source contained tiny overlapping boundary slivers; the normalized JSON assigns each such area once using stable-ID order, preserving the combined footprint without rounding or buffering. `topology` records this processing. This guarantee applies within the realtor collection; alternative neighborhood sources and district/park layers describe different concepts and must not be treated as additional mutually exclusive neighborhoods.
|
|
99
|
+
|
|
100
|
+
Run `pnpm data:normalize-realtor` when updating realtor geometry, then `pnpm data:catalog` and `pnpm check`. Cleanup aborts if it would erase a neighborhood, leave overlapping interiors, or change the combined footprint. Tests reject any nonempty polygon intersection; they do not excuse small slivers with an area threshold.
|
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
}
|